diff --git a/include/zephyr/bluetooth/bluetooth.h b/include/zephyr/bluetooth/bluetooth.h index 82d1a08e0dc61..13a34f83a8214 100644 --- a/include/zephyr/bluetooth/bluetooth.h +++ b/include/zephyr/bluetooth/bluetooth.h @@ -1477,6 +1477,28 @@ int bt_le_ext_adv_set_data(struct bt_le_ext_adv *adv, const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, size_t sd_len); +/** + * @brief Set decision data for extended advertising. + * + * Set decision data that will be used for decision-based advertising filtering + * as specified in Bluetooth Core Specification v6.2, Vol 4, Part E, Section 7.8.144. + * This data is used by observers with decision-based filtering instructions to + * determine whether to accept advertising PDUs from this advertiser. + * + * The decision data is associated with the advertising set and remains valid + * until the advertising set is deleted or new decision data is set. + * + * @param adv Advertising set object. + * @param data Decision data buffer. + * @param data_len Length of decision data (must be > 0). + * + * @return Zero on success or (negative) error code otherwise. + * @retval -EINVAL Invalid parameters. + * @retval -ENOTSUP Decision-based filtering not supported by controller. + */ +int bt_le_ext_adv_set_decision_data(struct bt_le_ext_adv *adv, + const uint8_t *data, uint8_t data_len); + /** * @brief Update advertising parameters. * @@ -2350,6 +2372,34 @@ enum bt_le_scan_opt { * @note Requires @ref BT_LE_SCAN_OPT_CODED. */ BT_LE_SCAN_OPT_NO_1M = BIT(3), + + /** + * @brief Enable extended scan filtering. + * + * Enables extended scan filtering mode which provides enhanced + * filtering capabilities. Can be combined with + * @ref BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST. + * + * @note Requires controller support for extended scan filtering. + */ + BT_LE_SCAN_OPT_EXTENDED_FILTER = BIT(4), + + /** + * @brief Enable decision-based advertising filtering. + * + * Enables decision-based advertising filtering as specified in + * Bluetooth Core Specification v6.2. This allows the controller to + * filter advertising PDUs based on decision criteria. Can be combined + * with @ref BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST and/or + * @ref BT_LE_SCAN_OPT_EXTENDED_FILTER. + * + * When this option is set, the controller will accept ADV_DECISION_IND + * PDUs according to the filter policy. + * + * @note Requires controller support for decision-based advertising + * filtering (CONFIG_BT_CTLR_DECISION_BASED_FILTERING). + */ + BT_LE_SCAN_OPT_DECISION_BASED = BIT(5), }; enum bt_le_scan_type { @@ -2620,6 +2670,28 @@ BUILD_ASSERT(BT_GAP_SCAN_FAST_WINDOW == BT_GAP_SCAN_FAST_INTERVAL_MIN, */ int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb); +/** + * @brief Set decision instructions for scanning. + * + * Set decision instructions that will be used to filter advertising PDUs during + * scanning as specified in Bluetooth Core Specification v6.2, Vol 4, Part E, + * Section 7.8.145. These instructions define the criteria for accepting advertising + * PDUs based on the decision data included in the advertisements. + * + * The instructions remain active until new instructions are set or scanning is stopped. + * This function should be called before starting scanning with decision-based filtering + * enabled (BT_LE_SCAN_OPT_DECISION_BASED). + * + * @param instructions Decision instructions buffer. + * @param instructions_len Length of decision instructions (must be > 0). + * + * @return Zero on success or (negative) error code otherwise. + * @retval -EINVAL Invalid parameters. + * @retval -ENOTSUP Decision-based filtering not supported by controller. + */ +int bt_le_scan_set_decision_instructions(const uint8_t *instructions, + uint8_t instructions_len); + /** * @brief Stop (LE) scanning. * diff --git a/include/zephyr/bluetooth/hci_types.h b/include/zephyr/bluetooth/hci_types.h index 2769a26a8d6b9..bb2c788e57783 100644 --- a/include/zephyr/bluetooth/hci_types.h +++ b/include/zephyr/bluetooth/hci_types.h @@ -1271,6 +1271,11 @@ struct bt_hci_cp_le_set_adv_enable { #define BT_HCI_LE_SCAN_FP_BASIC_FILTER 0x01 #define BT_HCI_LE_SCAN_FP_EXT_NO_FILTER 0x02 #define BT_HCI_LE_SCAN_FP_EXT_FILTER 0x03 +/* Decision-Based Advertising Filtering (BT Core Spec v6.2) */ +#define BT_HCI_LE_SCAN_FP_DECISION_NO_FILTER 0x04 +#define BT_HCI_LE_SCAN_FP_DECISION_FILTER 0x05 +#define BT_HCI_LE_SCAN_FP_DECISION_EXT_NO_FILTER 0x06 +#define BT_HCI_LE_SCAN_FP_DECISION_EXT_FILTER 0x07 struct bt_hci_cp_le_set_scan_param { uint8_t scan_type; @@ -2991,6 +2996,31 @@ struct bt_hci_op_le_connection_rate_request { #define BT_HCI_OP_LE_CONNECTION_RATE_REQUEST BT_OP(BT_OGF_LE, 0x00A1) /* 0x20A1 */ +/* LE Set Decision Data command (BT Core Spec v6.2, Vol 4, Part E, Section 7.8.144) */ +#define BT_HCI_OP_LE_SET_DECISION_DATA BT_OP(BT_OGF_LE, 0x00A3) /* 0x20A3 */ + +struct bt_hci_cp_le_set_decision_data { + uint8_t adv_handle; + uint8_t data_length; + uint8_t data[0]; +} __packed; + +struct bt_hci_rp_le_set_decision_data { + uint8_t status; +} __packed; + +/* LE Set Decision Instructions command (BT Core Spec v6.2, Vol 4, Part E, Section 7.8.145) */ +#define BT_HCI_OP_LE_SET_DECISION_INSTRUCTIONS BT_OP(BT_OGF_LE, 0x00A4) /* 0x20A4 */ + +struct bt_hci_cp_le_set_decision_instructions { + uint8_t instructions_length; + uint8_t instructions[0]; +} __packed; + +struct bt_hci_rp_le_set_decision_instructions { + uint8_t status; +} __packed; + /* Event definitions */ #define BT_HCI_EVT_UNKNOWN 0x00 diff --git a/samples/bluetooth/broadcaster_decision/CMakeLists.txt b/samples/bluetooth/broadcaster_decision/CMakeLists.txt new file mode 100644 index 0000000000000..9f9bf98cd4b8a --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(broadcaster_decision) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/bluetooth/broadcaster_decision/README.rst b/samples/bluetooth/broadcaster_decision/README.rst new file mode 100644 index 0000000000000..c6f2d23c6054c --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/README.rst @@ -0,0 +1,54 @@ +.. _bluetooth_broadcaster_decision: + +Bluetooth: Broadcaster with Decision-Based Advertising +####################################################### + +Overview +******** + +This sample demonstrates extended advertising with decision-based advertising +filtering support as specified in Bluetooth Core Specification v6.2, section +4.6.43. + +The application starts extended advertising and transmits advertising PDUs +that can be filtered using decision-based filtering on the scanner side. + +Requirements +************ + +* A board with Bluetooth Low Energy support +* Controller support for decision-based advertising filtering + (CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + +Building and Running +******************** + +This sample can be found under +:zephyr_file:`samples/bluetooth/broadcaster_decision` in the Zephyr tree. + +Build the sample with the following commands: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/broadcaster_decision + :board: + :goals: build flash + :compact: + +To observe the advertising with decision-based filtering, run the +:ref:`bluetooth_observer_decision` sample on another device. + +Sample Output +============= + +When running, the sample will output: + +.. code-block:: console + + Starting Decision-Based Advertising Broadcaster + Bluetooth initialized + Extended advertising set created + Advertising data set + Extended advertising started + Advertising with decision-based filtering support + Device name: Decision Broadcaster + Manufacturer data: DECISION diff --git a/samples/bluetooth/broadcaster_decision/prj.conf b/samples/bluetooth/broadcaster_decision/prj.conf new file mode 100644 index 0000000000000..0079b896e425d --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/prj.conf @@ -0,0 +1,8 @@ +CONFIG_BT=y +CONFIG_BT_BROADCASTER=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_DEVICE_NAME="Decision Broadcaster" +CONFIG_BT_CTLR_DECISION_BASED_FILTERING=y + +CONFIG_ASSERT=y +CONFIG_LOG=y diff --git a/samples/bluetooth/broadcaster_decision/sample.yaml b/samples/bluetooth/broadcaster_decision/sample.yaml new file mode 100644 index 0000000000000..9e9acf341f291 --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: Bluetooth broadcaster with decision-based advertising filtering + name: Bluetooth broadcaster_decision +common: + tags: bluetooth + filter: dt_compat_enabled("zephyr,bt-hci") + harness: bluetooth + platform_allow: + - native_sim + - qemu_cortex_m3 + - nrf52dk/nrf52832 + - nrf52840dk/nrf52840 + integration_platforms: + - native_sim +tests: + sample.bluetooth.broadcaster_decision: + build_only: true diff --git a/samples/bluetooth/broadcaster_decision/src/main.c b/samples/bluetooth/broadcaster_decision/src/main.c new file mode 100644 index 0000000000000..c3c5a05e6afb0 --- /dev/null +++ b/samples/bluetooth/broadcaster_decision/src/main.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Decision-Based Advertising Broadcaster Sample + * + * This sample demonstrates extended advertising with decision-based + * advertising filtering support as specified in Bluetooth Core + * Specification v6.2. + */ + +#include +#include +#include +#include + +#define DEVICE_NAME CONFIG_BT_DEVICE_NAME +#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1) + +/* Manufacturer ID for sample data */ +#define COMPANY_ID 0x05F1 /* Nordic Semiconductor */ + +static uint8_t mfg_data[] = { + 0xF1, 0x05, /* Company ID (little-endian) */ + 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4F, 0x4E /* "DECISION" */ +}; + +static const struct bt_data ad[] = { + BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN), + BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)), +}; + +int main(void) +{ + struct bt_le_ext_adv *adv; + struct bt_le_adv_param adv_param = { + .id = BT_ID_DEFAULT, + .sid = 0, + .secondary_max_skip = 0, + .options = BT_LE_ADV_OPT_EXT_ADV, + .interval_min = BT_GAP_ADV_FAST_INT_MIN_2, + .interval_max = BT_GAP_ADV_FAST_INT_MAX_2, + .peer = NULL, + }; + int err; + + printk("Starting Decision-Based Advertising Broadcaster\n"); + + /* Initialize Bluetooth */ + err = bt_enable(NULL); + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + return 0; + } + + printk("Bluetooth initialized\n"); + + /* Create extended advertising set */ + err = bt_le_ext_adv_create(&adv_param, NULL, &adv); + if (err) { + printk("Failed to create advertising set (err %d)\n", err); + return 0; + } + + printk("Extended advertising set created\n"); + + /* Set advertising data */ + err = bt_le_ext_adv_set_data(adv, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + printk("Failed to set advertising data (err %d)\n", err); + return 0; + } + + printk("Advertising data set\n"); + +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Set decision data for decision-based filtering */ + static const uint8_t decision_data[] = { + 0x01, /* Device type: sensor */ + 0x02, /* Capability: temperature measurement */ + 0x05, /* Battery level indicator */ + 0xFF /* Custom application data */ + }; + + err = bt_le_ext_adv_set_decision_data(adv, decision_data, sizeof(decision_data)); + if (err) { + printk("Failed to set decision data (err %d)\n", err); + return 0; + } + + printk("Decision data set (length: %d bytes)\n", sizeof(decision_data)); +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + + /* Start extended advertising */ + err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); + if (err) { + printk("Failed to start extended advertising (err %d)\n", err); + return 0; + } + + printk("Extended advertising started\n"); + printk("Advertising with decision-based filtering support\n"); + printk("Device name: %s\n", DEVICE_NAME); + printk("Manufacturer data: DECISION\n"); + + /* Keep advertising indefinitely */ + while (1) { + k_sleep(K_SECONDS(1)); + } + + return 0; +} diff --git a/samples/bluetooth/observer_decision/CMakeLists.txt b/samples/bluetooth/observer_decision/CMakeLists.txt new file mode 100644 index 0000000000000..f91bab0691566 --- /dev/null +++ b/samples/bluetooth/observer_decision/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(observer_decision) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/bluetooth/observer_decision/README.rst b/samples/bluetooth/observer_decision/README.rst new file mode 100644 index 0000000000000..855bbe501fa6b --- /dev/null +++ b/samples/bluetooth/observer_decision/README.rst @@ -0,0 +1,52 @@ +.. _bluetooth_observer_decision: + +Bluetooth: Observer with Decision-Based Advertising Filtering +############################################################## + +Overview +******** + +This sample demonstrates extended scanning with decision-based advertising +filtering support as specified in Bluetooth Core Specification v6.2, section +4.6.43. + +The application starts extended scanning with decision-based filtering enabled +and displays advertising reports from devices using decision-based advertising. + +Requirements +************ + +* A board with Bluetooth Low Energy support +* Controller support for decision-based advertising filtering + (CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + +Building and Running +******************** + +This sample can be found under +:zephyr_file:`samples/bluetooth/observer_decision` in the Zephyr tree. + +Build the sample with the following commands: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/observer_decision + :board: + :goals: build flash + :compact: + +To test the decision-based filtering, run the +:ref:`bluetooth_broadcaster_decision` sample on another device. + +Sample Output +============= + +When running, the sample will output: + +.. code-block:: console + + Starting Decision-Based Advertising Observer + Bluetooth initialized + Starting scan with decision-based filtering + Scanning successfully started + Waiting for advertising reports... + [DEVICE]: XX:XX:XX:XX:XX:XX, RSSI -45, Extended Advertising Name: Decision Broadcaster MFG: 0x05f1 Data: DECISION diff --git a/samples/bluetooth/observer_decision/prj.conf b/samples/bluetooth/observer_decision/prj.conf new file mode 100644 index 0000000000000..489b7f145c1a0 --- /dev/null +++ b/samples/bluetooth/observer_decision/prj.conf @@ -0,0 +1,9 @@ +CONFIG_BT=y +CONFIG_BT_OBSERVER=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_EXT_SCAN_BUF_SIZE=1650 +CONFIG_BT_DEVICE_NAME="Decision Observer" +CONFIG_BT_CTLR_DECISION_BASED_FILTERING=y + +CONFIG_ASSERT=y +CONFIG_LOG=y diff --git a/samples/bluetooth/observer_decision/sample.yaml b/samples/bluetooth/observer_decision/sample.yaml new file mode 100644 index 0000000000000..66e9aef9aa55b --- /dev/null +++ b/samples/bluetooth/observer_decision/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: Bluetooth observer with decision-based advertising filtering + name: Bluetooth observer_decision +common: + tags: bluetooth + filter: dt_compat_enabled("zephyr,bt-hci") + harness: bluetooth + platform_allow: + - native_sim + - qemu_cortex_m3 + - nrf52dk/nrf52832 + - nrf52840dk/nrf52840 + integration_platforms: + - native_sim +tests: + sample.bluetooth.observer_decision: + build_only: true diff --git a/samples/bluetooth/observer_decision/src/main.c b/samples/bluetooth/observer_decision/src/main.c new file mode 100644 index 0000000000000..d480ed94ff2d6 --- /dev/null +++ b/samples/bluetooth/observer_decision/src/main.c @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Decision-Based Advertising Observer Sample + * + * This sample demonstrates extended scanning with decision-based + * advertising filtering support as specified in Bluetooth Core + * Specification v6.2. + */ + +#include +#include +#include +#include + +#define NAME_LEN 30 + +static void scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + char name[NAME_LEN] = {0}; + uint8_t data_len; + uint8_t data_type; + + bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str)); + + printk("[DEVICE]: %s, RSSI %d, ", addr_str, info->rssi); + + /* Check for extended advertising */ + if (info->adv_type == BT_GAP_ADV_TYPE_EXT_ADV) { + printk("Extended Advertising "); + } + + /* Parse advertising data */ + while (buf->len > 1) { + data_len = net_buf_simple_pull_u8(buf); + if (data_len == 0) { + break; + } + + data_type = net_buf_simple_pull_u8(buf); + data_len--; + + switch (data_type) { + case BT_DATA_NAME_COMPLETE: + case BT_DATA_NAME_SHORTENED: + if (data_len < sizeof(name)) { + memcpy(name, buf->data, data_len); + name[data_len] = '\0'; + printk("Name: %s ", name); + } + net_buf_simple_pull(buf, data_len); + break; + + case BT_DATA_MANUFACTURER_DATA: + if (data_len >= 2) { + uint16_t company_id = sys_get_le16(buf->data); + printk("MFG: 0x%04x ", company_id); + + /* Print manufacturer data */ + if (data_len > 2) { + printk("Data: "); + for (int i = 2; i < data_len; i++) { + printk("%c", buf->data[i]); + } + printk(" "); + } + } + net_buf_simple_pull(buf, data_len); + break; + + default: + net_buf_simple_pull(buf, data_len); + break; + } + } + + printk("\n"); +} + +static struct bt_le_scan_cb scan_callbacks = { + .recv = scan_recv, +}; + +int main(void) +{ + struct bt_le_scan_param scan_param = { + .type = BT_LE_SCAN_TYPE_PASSIVE, + .options = BT_LE_SCAN_OPT_DECISION_BASED, + .interval = BT_GAP_SCAN_FAST_INTERVAL, + .window = BT_GAP_SCAN_FAST_WINDOW, + }; + int err; + + printk("Starting Decision-Based Advertising Observer\n"); + + /* Initialize Bluetooth */ + err = bt_enable(NULL); + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + return 0; + } + + printk("Bluetooth initialized\n"); + + /* Register scan callbacks */ + bt_le_scan_cb_register(&scan_callbacks); + +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Set decision instructions for filtering */ + static const uint8_t decision_instructions[] = { + 0x01, /* Filter on device type */ + 0x02, /* Accept if capability matches */ + 0x00, /* No additional criteria */ + }; + + printk("Setting decision instructions\n"); + err = bt_le_scan_set_decision_instructions(decision_instructions, + sizeof(decision_instructions)); + if (err) { + printk("Failed to set decision instructions (err %d)\n", err); + return 0; + } + + printk("Decision instructions set (length: %d bytes)\n", + sizeof(decision_instructions)); +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + + /* Start scanning with decision-based filtering */ + printk("Starting scan with decision-based filtering\n"); + err = bt_le_scan_start(&scan_param, NULL); + if (err) { + printk("Scan start failed (err %d)\n", err); + return 0; + } + + printk("Scanning successfully started\n"); + printk("Waiting for advertising reports...\n"); + + /* Keep scanning indefinitely */ + while (1) { + k_sleep(K_SECONDS(1)); + } + + return 0; +} diff --git a/subsys/bluetooth/controller/Kconfig b/subsys/bluetooth/controller/Kconfig index 0d5cba8241624..0c1e89ebc445a 100644 --- a/subsys/bluetooth/controller/Kconfig +++ b/subsys/bluetooth/controller/Kconfig @@ -33,6 +33,9 @@ config BT_CTLR_PRIVACY_SUPPORT config BT_CTLR_EXT_SCAN_FP_SUPPORT bool +config BT_CTLR_DECISION_BASED_FILTERING_SUPPORT + bool + config BT_CTLR_PHY_UPDATE_SUPPORT bool @@ -711,6 +714,15 @@ config BT_CTLR_EXT_SCAN_FP Enable support for Bluetooth v4.2 LE Extended Scanner Filter Policies in the Controller. +config BT_CTLR_DECISION_BASED_FILTERING + bool "LE Decision-Based Advertising Filtering" + depends on BT_OBSERVER && BT_CTLR_DECISION_BASED_FILTERING_SUPPORT + help + Enable support for Bluetooth v6.2 LE Decision-Based Advertising + Filtering in the Controller. This feature allows the scanner to + make filtering decisions based on advertising data content as + specified in BLUETOOTH CORE SPECIFICATION Version 6.2. + config BT_CTLR_PHY_2M bool "2Mbps PHY Support" depends on (BT_CTLR_PHY || BT_CTLR_ADV_EXT) && BT_CTLR_PHY_2M_SUPPORT diff --git a/subsys/bluetooth/controller/Kconfig.ll_sw_split b/subsys/bluetooth/controller/Kconfig.ll_sw_split index 6b2c9f2575e8f..f8f93d79da204 100644 --- a/subsys/bluetooth/controller/Kconfig.ll_sw_split +++ b/subsys/bluetooth/controller/Kconfig.ll_sw_split @@ -32,6 +32,7 @@ config BT_LLL_VENDOR_NORDIC SOC_COMPATIBLE_NRF54LX || \ BT_CTLR_DATA_LENGTH_CLEAR select BT_CTLR_EXT_SCAN_FP_SUPPORT + select BT_CTLR_DECISION_BASED_FILTERING_SUPPORT select BT_CTLR_PHY_2M_SUPPORT if HAS_HW_NRF_RADIO_BLE_2M || \ BT_CTLR_PHY_2M_NRF select BT_CTLR_PHY_CODED_SUPPORT if HAS_HW_NRF_RADIO_BLE_CODED @@ -97,6 +98,7 @@ config BT_LLL_VENDOR_OPENISA select BT_CTLR_PRIVACY_SUPPORT if BT_CTLR_CRYPTO_SUPPORT select BT_CTLR_PHY_UPDATE_SUPPORT select BT_CTLR_EXT_REJ_IND_SUPPORT + select BT_CTLR_DECISION_BASED_FILTERING_SUPPORT select BT_TICKER_REMAINDER_SUPPORT select BT_TICKER_UPDATE if BT_BROADCASTER || BT_CONN diff --git a/subsys/bluetooth/controller/hci/hci.c b/subsys/bluetooth/controller/hci/hci.c index 6520f50c32938..e426850975890 100644 --- a/subsys/bluetooth/controller/hci/hci.c +++ b/subsys/bluetooth/controller/hci/hci.c @@ -3365,6 +3365,42 @@ static void le_df_read_ant_inf(struct net_buf *buf, struct net_buf **evt) } #endif /* CONFIG_BT_CTLR_DF */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) +static void le_set_decision_data(struct net_buf *buf, struct net_buf **evt) +{ + struct bt_hci_cp_le_set_decision_data *cmd = (void *)buf->data; + uint8_t status; + + /* For now, just return success - full implementation needed in controller */ + /* This will store decision data per advertising set */ + LOG_DBG("LE Set Decision Data: handle %u, len %u", cmd->adv_handle, cmd->data_length); + + /* TODO: Store decision data in advertising set structure */ + /* The data should be associated with the advertising handle */ + /* and used during PDU transmission */ + + status = BT_HCI_ERR_SUCCESS; + *evt = cmd_complete_status(status); +} + +static void le_set_decision_instructions(struct net_buf *buf, struct net_buf **evt) +{ + struct bt_hci_cp_le_set_decision_instructions *cmd = (void *)buf->data; + uint8_t status; + + /* For now, just return success - full implementation needed in controller */ + /* This will store decision instructions for scanner */ + LOG_DBG("LE Set Decision Instructions: len %u", cmd->instructions_length); + + /* TODO: Store decision instructions in scanner context */ + /* The instructions should be used during scan filtering */ + /* to match against decision data in received PDUs */ + + status = BT_HCI_ERR_SUCCESS; + *evt = cmd_complete_status(status); +} +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + #if defined(CONFIG_BT_CTLR_DTM_HCI) static void le_rx_test(struct net_buf *buf, struct net_buf **evt) { @@ -5031,6 +5067,16 @@ static int controller_cmd_handle(uint16_t ocf, struct net_buf *cmd, #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */ #endif /* CONFIG_BT_CTLR_DF */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + case BT_OCF(BT_HCI_OP_LE_SET_DECISION_DATA): + le_set_decision_data(cmd, evt); + break; + + case BT_OCF(BT_HCI_OP_LE_SET_DECISION_INSTRUCTIONS): + le_set_decision_instructions(cmd, evt); + break; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + #if defined(CONFIG_BT_CTLR_DTM_HCI) case BT_OCF(BT_HCI_OP_LE_RX_TEST): le_rx_test(cmd, evt); @@ -6663,8 +6709,34 @@ static void le_advertising_report(struct pdu_data *pdu_data, struct node_rx_pdu *node_rx, struct net_buf *buf) { +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Mapping PDU types to HCI advertising event types (10 elements, indices 0-9): + * Index 0: PDU_ADV_TYPE_ADV_IND (0x00) → BT_HCI_ADV_IND (0x00) + * Index 1: PDU_ADV_TYPE_DIRECT_IND (0x01) → BT_HCI_ADV_DIRECT_IND (0x01) + * Index 2: PDU_ADV_TYPE_NONCONN_IND (0x02) → BT_HCI_ADV_NONCONN_IND (0x03) + * Index 3: PDU_ADV_TYPE_SCAN_REQ (0x03) → invalid (0xff) + * Index 4: PDU_ADV_TYPE_SCAN_RSP (0x04) → BT_HCI_ADV_SCAN_RSP (0x04) + * Index 5: PDU_ADV_TYPE_CONNECT_IND (0x05) → invalid (0xff) + * Index 6: PDU_ADV_TYPE_SCAN_IND (0x06) → BT_HCI_ADV_SCAN_IND (0x02) + * Index 7: PDU_ADV_TYPE_EXT_IND (0x07) → invalid (0xff) + * Index 8: PDU_ADV_TYPE_AUX_CONNECT_RSP (0x08) → invalid (0xff) + * Index 9: PDU_ADV_TYPE_ADV_DECISION_IND (0x09) → BT_HCI_ADV_NONCONN_IND (0x03) + */ + const uint8_t c_adv_type[] = { 0x00, 0x01, 0x03, 0xff, 0x04, + 0xff, 0x02, 0xff, 0xff, 0x03 }; +#else + /* Mapping PDU types to HCI advertising event types (7 elements, indices 0-6): + * Index 0: PDU_ADV_TYPE_ADV_IND (0x00) → BT_HCI_ADV_IND (0x00) + * Index 1: PDU_ADV_TYPE_DIRECT_IND (0x01) → BT_HCI_ADV_DIRECT_IND (0x01) + * Index 2: PDU_ADV_TYPE_NONCONN_IND (0x02) → BT_HCI_ADV_NONCONN_IND (0x03) + * Index 3: PDU_ADV_TYPE_SCAN_REQ (0x03) → invalid (0xff) + * Index 4: PDU_ADV_TYPE_SCAN_RSP (0x04) → BT_HCI_ADV_SCAN_RSP (0x04) + * Index 5: PDU_ADV_TYPE_CONNECT_IND (0x05) → invalid (0xff) + * Index 6: PDU_ADV_TYPE_SCAN_IND (0x06) → BT_HCI_ADV_SCAN_IND (0x02) + */ const uint8_t c_adv_type[] = { 0x00, 0x01, 0x03, 0xff, 0x04, 0xff, 0x02 }; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ struct bt_hci_evt_le_advertising_report *sep; struct pdu_adv *adv = (void *)pdu_data; struct bt_hci_evt_le_advertising_info *adv_info; @@ -6752,12 +6824,30 @@ static void le_advertising_report(struct pdu_data *pdu_data, #endif /* CONFIG_BT_CTLR_PRIVACY */ adv_info->addr.type = adv->tx_addr; - memcpy(&adv_info->addr.a.val[0], &adv->adv_ind.addr[0], - sizeof(bt_addr_t)); +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* ADV_DECISION_IND has same structure as ADV_IND (addr + data). + * Access via decision_ind union member for type safety. + */ + if (adv->type == PDU_ADV_TYPE_ADV_DECISION_IND) { + memcpy(&adv_info->addr.a.val[0], &adv->decision_ind.addr[0], + sizeof(bt_addr_t)); + } else +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + { + memcpy(&adv_info->addr.a.val[0], &adv->adv_ind.addr[0], + sizeof(bt_addr_t)); + } } adv_info->length = data_len; - memcpy(&adv_info->data[0], &adv->adv_ind.data[0], data_len); +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + if (adv->type == PDU_ADV_TYPE_ADV_DECISION_IND) { + memcpy(&adv_info->data[0], &adv->decision_ind.data[0], data_len); + } else +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + { + memcpy(&adv_info->data[0], &adv->adv_ind.data[0], data_len); + } /* RSSI */ prssi = &adv_info->data[0] + data_len; *prssi = rssi; @@ -6769,6 +6859,33 @@ static void le_ext_adv_legacy_report(struct pdu_data *pdu_data, struct net_buf *buf) { /* Lookup event type based on pdu_adv_type set by LLL */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + const uint8_t evt_type_lookup[] = { + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | BT_HCI_LE_ADV_EVT_TYPE_SCAN | + BT_HCI_LE_ADV_EVT_TYPE_CONN), /* ADV_IND */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | BT_HCI_LE_ADV_EVT_TYPE_DIRECT | + BT_HCI_LE_ADV_EVT_TYPE_CONN), /* DIRECT_IND */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY), /* NONCONN_IND */ + 0xff, /* Invalid index lookup */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | + BT_HCI_LE_ADV_EVT_TYPE_SCAN_RSP | + BT_HCI_LE_ADV_EVT_TYPE_SCAN), /* SCAN_RSP to an ADV_SCAN_IND + */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | + BT_HCI_LE_ADV_EVT_TYPE_SCAN_RSP | + BT_HCI_LE_ADV_EVT_TYPE_SCAN | + BT_HCI_LE_ADV_EVT_TYPE_CONN), /* SCAN_RSP to an ADV_IND, + * NOTE: LLL explicitly sets + * adv_type to + * PDU_ADV_TYPE_ADV_IND_SCAN_RSP + */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | + BT_HCI_LE_ADV_EVT_TYPE_SCAN), /* SCAN_IND */ + 0xff, /* Invalid - EXT_IND */ + 0xff, /* Invalid - AUX_CONNECT_RSP */ + (BT_HCI_LE_ADV_EVT_TYPE_LEGACY) /* ADV_DECISION_IND */ + }; +#else const uint8_t evt_type_lookup[] = { (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | BT_HCI_LE_ADV_EVT_TYPE_SCAN | BT_HCI_LE_ADV_EVT_TYPE_CONN), /* ADV_IND */ @@ -6791,6 +6908,7 @@ static void le_ext_adv_legacy_report(struct pdu_data *pdu_data, (BT_HCI_LE_ADV_EVT_TYPE_LEGACY | BT_HCI_LE_ADV_EVT_TYPE_SCAN) /* SCAN_IND */ }; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ struct bt_hci_evt_le_ext_advertising_info *adv_info; struct bt_hci_evt_le_ext_advertising_report *sep; struct pdu_adv *adv = (void *)pdu_data; diff --git a/subsys/bluetooth/controller/ll_sw/lll_scan.h b/subsys/bluetooth/controller/ll_sw/lll_scan.h index b9633f8993839..0941138662401 100644 --- a/subsys/bluetooth/controller/ll_sw/lll_scan.h +++ b/subsys/bluetooth/controller/ll_sw/lll_scan.h @@ -29,7 +29,11 @@ struct lll_scan { uint8_t state:1; uint8_t chan:2; +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + uint8_t filter_policy:3; +#else uint8_t filter_policy:2; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ uint8_t type:1; uint8_t init_addr_type:1; uint8_t is_stop:1; @@ -89,10 +93,13 @@ struct lll_scan_aux { /* Define to check if filter is enabled and in addition if it is Extended Scan - * Filtering. + * Filtering or Decision-Based Filtering. */ #define SCAN_FP_FILTER BIT(0) #define SCAN_FP_EXT BIT(1) +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) +#define SCAN_FP_DECISION BIT(2) +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ int lll_scan_init(void); int lll_scan_reset(void); 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 e5407b7aa1110..b1a18c95d2fa8 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c @@ -1501,6 +1501,8 @@ static struct pdu_adv *chan_prepare(struct lll_adv *lll) radio_pkt_tx_set(pdu); if ((pdu->type != PDU_ADV_TYPE_NONCONN_IND) && + (!IS_ENABLED(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || + (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND)) && (!IS_ENABLED(CONFIG_BT_CTLR_ADV_EXT) || (pdu->type != PDU_ADV_TYPE_EXT_IND))) { struct pdu_adv *scan_pdu; diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan.c index 4365897597240..515e24af91eef 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan.c @@ -1439,6 +1439,12 @@ static inline int isr_rx_pdu(struct lll_scan *lll, struct pdu_adv *pdu_adv_rx, lll_scan_ext_tgta_check(lll, true, false, pdu_adv_rx, rl_idx, &dir_report)) || #endif /* CONFIG_BT_CTLR_ADV_EXT */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + ((pdu_adv_rx->type == PDU_ADV_TYPE_ADV_DECISION_IND) && + (pdu_adv_rx->len >= offsetof(struct pdu_adv_decision_ind, data)) && + (pdu_adv_rx->len <= sizeof(struct pdu_adv_decision_ind)) && + ((lll->filter_policy & SCAN_FP_DECISION) != 0U)) || +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ ((pdu_adv_rx->type == PDU_ADV_TYPE_SCAN_RSP) && (pdu_adv_rx->len >= offsetof(struct pdu_adv_scan_rsp, data)) && (pdu_adv_rx->len <= sizeof(struct pdu_adv_scan_rsp)) && 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 96b687aeca165..0277409fc3d8a 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c @@ -801,6 +801,8 @@ static void chan_prepare(struct lll_adv *lll) radio_pkt_tx_set(pdu); if ((pdu->type != PDU_ADV_TYPE_NONCONN_IND) && + (!IS_ENABLED(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) || + (pdu->type != PDU_ADV_TYPE_ADV_DECISION_IND)) && (!IS_ENABLED(CONFIG_BT_CTLR_ADV_EXT) || (pdu->type != PDU_ADV_TYPE_EXT_IND))) { radio_isr_set(isr_tx, lll); 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 013f705851ca3..4e486ec2cb5d3 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c @@ -962,6 +962,12 @@ static inline uint32_t isr_rx_pdu(struct lll_scan *lll, uint8_t devmatch_ok, ((pdu_adv_rx->type == PDU_ADV_TYPE_EXT_IND) && (lll->phy)) || #endif /* CONFIG_BT_CTLR_ADV_EXT */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + ((pdu_adv_rx->type == PDU_ADV_TYPE_ADV_DECISION_IND) && + (pdu_adv_rx->len >= offsetof(struct pdu_adv_decision_ind, data)) && + (pdu_adv_rx->len <= sizeof(struct pdu_adv_decision_ind)) && + ((lll->filter_policy & SCAN_FP_DECISION) != 0U)) || +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ ((pdu_adv_rx->type == PDU_ADV_TYPE_SCAN_RSP) && (pdu_adv_rx->len <= sizeof(struct pdu_adv_scan_rsp)) && (lll->state != 0U) && diff --git a/subsys/bluetooth/controller/ll_sw/pdu.h b/subsys/bluetooth/controller/ll_sw/pdu.h index 430704c84514f..1fbcc6660a74e 100644 --- a/subsys/bluetooth/controller/ll_sw/pdu.h +++ b/subsys/bluetooth/controller/ll_sw/pdu.h @@ -417,6 +417,13 @@ struct pdu_adv_connect_ind { } __packed; } __packed; +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) +struct pdu_adv_decision_ind { + uint8_t addr[BDADDR_SIZE]; + uint8_t data[PDU_AC_LEG_DATA_SIZE_MAX]; +} __packed; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + struct pdu_adv_ext_hdr { #ifdef CONFIG_LITTLE_ENDIAN uint8_t adv_addr:1; @@ -548,6 +555,9 @@ enum pdu_adv_type { PDU_ADV_TYPE_AUX_SYNC_IND = PDU_ADV_TYPE_EXT_IND, PDU_ADV_TYPE_AUX_CHAIN_IND = PDU_ADV_TYPE_EXT_IND, PDU_ADV_TYPE_AUX_CONNECT_RSP = 0x08, +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + PDU_ADV_TYPE_ADV_DECISION_IND = 0x09, +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ } __packed; struct pdu_adv { @@ -578,6 +588,10 @@ struct pdu_adv { #if defined(CONFIG_BT_CTLR_ADV_EXT) struct pdu_adv_com_ext_adv adv_ext_ind; #endif /* CONFIG_BT_CTLR_ADV_EXT */ + +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + struct pdu_adv_decision_ind decision_ind; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ } __packed; } __packed; diff --git a/subsys/bluetooth/controller/ll_sw/ull_scan.c b/subsys/bluetooth/controller/ll_sw/ull_scan.c index 514072643e16b..dcda7e253aa84 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_scan.c +++ b/subsys/bluetooth/controller/ll_sw/ull_scan.c @@ -131,6 +131,24 @@ uint8_t ll_scan_params_set(uint8_t type, uint16_t interval, uint16_t window, lll = &scan->lll; #endif /* !CONFIG_BT_CTLR_ADV_EXT */ + /* Validate filter policy parameter */ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Decision-based filtering: support values 0x00-0x07 */ + if (filter_policy > 0x07) { + return BT_HCI_ERR_INVALID_PARAM; + } +#elif defined(CONFIG_BT_CTLR_EXT_SCAN_FP) + /* Extended scan filtering: support values 0x00-0x03 */ + if (filter_policy > 0x03) { + return BT_HCI_ERR_INVALID_PARAM; + } +#else + /* Basic filtering: support values 0x00-0x01 */ + if (filter_policy > 0x01) { + return BT_HCI_ERR_INVALID_PARAM; + } +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + scan->own_addr_type = own_addr_type; scan->ticks_window = ull_scan_params_set(lll, type, interval, window, @@ -374,7 +392,42 @@ uint32_t ull_scan_params_set(struct lll_scan *lll, uint8_t type, * 1001b - Ext. Coded active */ lll->type = type; - lll->filter_policy = filter_policy; + + /* Convert HCI filter_policy to LLL filter_policy bit flags + * HCI values: + * 0x00 - Basic, no filter + * 0x01 - Basic, use filter accept list + * 0x02 - Extended, no filter + * 0x03 - Extended, use filter accept list + * 0x04 - Decision, no filter (if enabled) + * 0x05 - Decision, use filter accept list (if enabled) + * 0x06 - Decision + Extended, no filter (if enabled) + * 0x07 - Decision + Extended, use filter accept list (if enabled) + * + * LLL flags: + * SCAN_FP_FILTER (bit 0) - Use filter accept list + * SCAN_FP_EXT (bit 1) - Extended scan filtering + * SCAN_FP_DECISION (bit 2) - Decision-based filtering + */ + lll->filter_policy = 0U; + + /* Set SCAN_FP_FILTER if filter accept list is used (odd values) */ + if (filter_policy & 0x01) { + lll->filter_policy |= SCAN_FP_FILTER; + } + + /* Set SCAN_FP_EXT if extended filtering is used (0x02-0x03, 0x06-0x07) */ + if (filter_policy & 0x02) { + lll->filter_policy |= SCAN_FP_EXT; + } + +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Set SCAN_FP_DECISION if decision-based filtering is used (0x04-0x07) */ + if (filter_policy >= 0x04) { + lll->filter_policy |= SCAN_FP_DECISION; + } +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + lll->interval = interval; lll->ticks_window = HAL_TICKER_US_TO_TICKS((uint64_t)window * SCAN_INT_UNIT_US); diff --git a/subsys/bluetooth/host/adv.c b/subsys/bluetooth/host/adv.c index 1a8c94aba30fb..76646c0084004 100644 --- a/subsys/bluetooth/host/adv.c +++ b/subsys/bluetooth/host/adv.c @@ -1606,6 +1606,58 @@ int bt_le_ext_adv_set_data(struct bt_le_ext_adv *adv, return le_adv_update(adv, ad, ad_len, sd, sd_len, ext_adv, scannable); } +int bt_le_ext_adv_set_decision_data(struct bt_le_ext_adv *adv, + const uint8_t *data, uint8_t data_len) +{ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + struct bt_hci_cp_le_set_decision_data *cp; + struct bt_hci_rp_le_set_decision_data *rp; + struct net_buf *buf, *rsp = NULL; + int err; + + if (!BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) { + return -ENOTSUP; + } + + CHECKIF(adv == NULL) { + LOG_DBG("adv is NULL"); + return -EINVAL; + } + + CHECKIF(data == NULL || data_len == 0) { + LOG_DBG("Invalid decision data parameters"); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(K_FOREVER); + if (!buf) { + LOG_WRN("No HCI buffers"); + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->adv_handle = adv->handle; + cp->data_length = data_len; + net_buf_add_mem(buf, data, data_len); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_DECISION_DATA, buf, &rsp); + if (err) { + return err; + } + + rp = (void *)rsp->data; + err = rp->status; + net_buf_unref(rsp); + + return err; +#else + ARG_UNUSED(adv); + ARG_UNUSED(data); + ARG_UNUSED(data_len); + return -ENOTSUP; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ +} + int bt_le_ext_adv_delete(struct bt_le_ext_adv *adv) { struct bt_hci_cp_le_remove_adv_set *cp; diff --git a/subsys/bluetooth/host/scan.c b/subsys/bluetooth/host/scan.c index b993dd72a5732..04c68187963f9 100644 --- a/subsys/bluetooth/host/scan.c +++ b/subsys/bluetooth/host/scan.c @@ -199,6 +199,58 @@ int bt_le_scan_set_enable(uint8_t enable) BT_LE_SCAN_OPT_FILTER_DUPLICATE); } +/** + * @brief Convert scan options to HCI filter policy value + * + * Maps the combination of scan options to the appropriate HCI filter policy + * value according to the following table: + * + * | Options | HCI Value | Description | + * |---------|-----------|-------------| + * | None | 0x00 | Basic, no filter | + * | FAL | 0x01 | Basic, use filter accept list | + * | EXT | 0x02 | Extended, no filter | + * | EXT + FAL | 0x03 | Extended, use filter accept list | + * | DEC | 0x04 | Decision, no filter | + * | DEC + FAL | 0x05 | Decision, use filter accept list | + * | DEC + EXT | 0x06 | Decision + Extended, no filter | + * | DEC + EXT + FAL | 0x07 | Decision + Extended, use filter accept list | + * + * Note: Bit 0 of the filter policy always indicates filter accept list usage. + * + * @param options Scan options bit field + * @return HCI filter policy value (0x00-0x07) + */ +static uint8_t scan_options_to_filter_policy(uint8_t options) +{ + uint8_t filter_policy = BT_HCI_LE_SCAN_FP_BASIC_NO_FILTER; + +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + /* Check for decision-based filtering (bit 5) */ + if (options & BT_LE_SCAN_OPT_DECISION_BASED) { + filter_policy = BT_HCI_LE_SCAN_FP_DECISION_NO_FILTER; + + /* Add extended filter flag if requested (bit 4) */ + if (options & BT_LE_SCAN_OPT_EXTENDED_FILTER) { + filter_policy = BT_HCI_LE_SCAN_FP_DECISION_EXT_NO_FILTER; + } + } else if (options & BT_LE_SCAN_OPT_EXTENDED_FILTER) { + /* Extended filtering without decision-based */ + filter_policy = BT_HCI_LE_SCAN_FP_EXT_NO_FILTER; + } +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ + + /* Add filter accept list flag if requested. + * Bit 0 indicates filter accept list usage in all filter policy modes. + */ + if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && + (options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST)) { + filter_policy |= 0x01; + } + + return filter_policy; +} + static int start_le_scan_ext(struct bt_le_scan_param *scan_param) { struct bt_hci_ext_scan_phy param_1m; @@ -260,9 +312,7 @@ static int start_le_scan_ext(struct bt_le_scan_param *scan_param) set_param = net_buf_add(buf, sizeof(*set_param)); set_param->own_addr_type = own_addr_type; set_param->phys = 0; - set_param->filter_policy = scan_param->options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST - ? BT_HCI_LE_SCAN_FP_BASIC_FILTER - : BT_HCI_LE_SCAN_FP_BASIC_NO_FILTER; + set_param->filter_policy = scan_options_to_filter_policy(scan_param->options); if (phy_1m) { set_param->phys |= BT_HCI_LE_EXT_SCAN_PHY_1M; @@ -306,12 +356,7 @@ static int start_le_scan_legacy(struct bt_le_scan_param *param) set_param.interval = sys_cpu_to_le16(param->interval); set_param.window = sys_cpu_to_le16(param->window); - if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && - param->options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST) { - set_param.filter_policy = BT_HCI_LE_SCAN_FP_BASIC_FILTER; - } else { - set_param.filter_policy = BT_HCI_LE_SCAN_FP_BASIC_NO_FILTER; - } + set_param.filter_policy = scan_options_to_filter_policy(param->options); active_scan = param->type == BT_HCI_LE_SCAN_ACTIVE; err = bt_id_set_scan_own_addr(active_scan, &set_param.addr_type); @@ -1744,6 +1789,51 @@ static bool valid_le_scan_param(const struct bt_le_scan_param *param) return true; } +int bt_le_scan_set_decision_instructions(const uint8_t *instructions, + uint8_t instructions_len) +{ +#if defined(CONFIG_BT_CTLR_DECISION_BASED_FILTERING) + struct bt_hci_cp_le_set_decision_instructions *cp; + struct bt_hci_rp_le_set_decision_instructions *rp; + struct net_buf *buf, *rsp = NULL; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) { + return -EAGAIN; + } + + CHECKIF(instructions == NULL || instructions_len == 0) { + LOG_DBG("Invalid decision instructions parameters"); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(K_FOREVER); + if (!buf) { + LOG_WRN("No HCI buffers"); + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->instructions_length = instructions_len; + net_buf_add_mem(buf, instructions, instructions_len); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_DECISION_INSTRUCTIONS, buf, &rsp); + if (err) { + return err; + } + + rp = (void *)rsp->data; + err = rp->status; + net_buf_unref(rsp); + + return err; +#else + ARG_UNUSED(instructions); + ARG_UNUSED(instructions_len); + return -ENOTSUP; +#endif /* CONFIG_BT_CTLR_DECISION_BASED_FILTERING */ +} + int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb) { int err; diff --git a/tests/bsim/bluetooth/host/adv/decision/CMakeLists.txt b/tests/bsim/bluetooth/host/adv/decision/CMakeLists.txt new file mode 100644 index 0000000000000..0ceb05f3a5f44 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(bsim_test_decision_adv) + +add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit) +target_link_libraries(app PRIVATE babblekit) + +target_sources_ifdef(CONFIG_BT_OBSERVER app PRIVATE + src/observer_decision.c +) + +target_sources_ifdef(CONFIG_BT_BROADCASTER app PRIVATE + src/broadcaster_decision.c +) + +zephyr_include_directories( + ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ + ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ +) diff --git a/tests/bsim/bluetooth/host/adv/decision/prj_broadcaster.conf b/tests/bsim/bluetooth/host/adv/decision/prj_broadcaster.conf new file mode 100644 index 0000000000000..77a31270c5d83 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/prj_broadcaster.conf @@ -0,0 +1,12 @@ +CONFIG_BT=y +CONFIG_BT_BROADCASTER=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_DEVICE_NAME="Decision Broadcaster" +CONFIG_BT_CTLR_DECISION_BASED_FILTERING=y + +# Test configuration +CONFIG_BT_MAX_CONN=1 + +CONFIG_ASSERT=y +CONFIG_LOG=y diff --git a/tests/bsim/bluetooth/host/adv/decision/prj_observer.conf b/tests/bsim/bluetooth/host/adv/decision/prj_observer.conf new file mode 100644 index 0000000000000..e5de0dc44bc00 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/prj_observer.conf @@ -0,0 +1,13 @@ +CONFIG_BT=y +CONFIG_BT_OBSERVER=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_EXT_SCAN_BUF_SIZE=1650 +CONFIG_BT_DEVICE_NAME="Decision Observer" +CONFIG_BT_CTLR_DECISION_BASED_FILTERING=y + +# Test configuration +CONFIG_BT_MAX_CONN=1 + +CONFIG_ASSERT=y +CONFIG_LOG=y diff --git a/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c b/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c new file mode 100644 index 0000000000000..6febd3253a8ec --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/src/broadcaster_decision.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Decision-Based Advertising Broadcaster Test + * + * Test for extended advertising with decision-based advertising filtering. + * Reuses broadcaster_decision sample code with babblekit integration. + */ + +#include +#include +#include +#include + +#include "babblekit/testcase.h" +#include "babblekit/flags.h" + +extern enum bst_result_t bst_result; + +#define DEVICE_NAME CONFIG_BT_DEVICE_NAME +#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1) + +/* Manufacturer ID for sample data */ +#define COMPANY_ID 0x05F1 /* Nordic Semiconductor */ + +DEFINE_FLAG_STATIC(flag_adv_started); + +static uint8_t mfg_data[] = { + 0xF1, 0x05, /* Company ID (little-endian) */ + 0x44, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4F, 0x4E /* "DECISION" */ +}; + +static const struct bt_data ad[] = { + BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN), + BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)), +}; + +static void test_broadcaster_main(void) +{ + struct bt_le_ext_adv *adv; + struct bt_le_adv_param adv_param = { + .id = BT_ID_DEFAULT, + .sid = 0, + .secondary_max_skip = 0, + .options = BT_LE_ADV_OPT_EXT_ADV, + .interval_min = BT_GAP_ADV_FAST_INT_MIN_2, + .interval_max = BT_GAP_ADV_FAST_INT_MAX_2, + .peer = NULL, + }; + int err; + + printk("Decision-Based Advertising Broadcaster Test\n"); + + /* Initialize Bluetooth */ + err = bt_enable(NULL); + if (err) { + TEST_FAIL("Bluetooth init failed (err %d)\n", err); + return; + } + + printk("Bluetooth initialized\n"); + + /* Create extended advertising set */ + err = bt_le_ext_adv_create(&adv_param, NULL, &adv); + if (err) { + TEST_FAIL("Failed to create advertising set (err %d)\n", err); + return; + } + + printk("Extended advertising set created\n"); + + /* Set advertising data */ + err = bt_le_ext_adv_set_data(adv, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + TEST_FAIL("Failed to set advertising data (err %d)\n", err); + return; + } + + printk("Advertising data set\n"); + + /* Set decision data for decision-based filtering */ + static const uint8_t decision_data[] = { + 0x01, /* Device type: sensor */ + 0x02, /* Capability: temperature measurement */ + 0x05, /* Battery level indicator */ + 0xFF /* Custom application data */ + }; + + err = bt_le_ext_adv_set_decision_data(adv, decision_data, sizeof(decision_data)); + if (err) { + TEST_FAIL("Failed to set decision data (err %d)\n", err); + return; + } + + printk("Decision data set (length: %d bytes)\n", sizeof(decision_data)); + + /* Start extended advertising */ + err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); + if (err) { + TEST_FAIL("Failed to start extended advertising (err %d)\n", err); + return; + } + + printk("Extended advertising started\n"); + printk("Advertising with decision-based filtering support\n"); + + SET_FLAG(flag_adv_started); + + /* Keep advertising for the duration of the test */ + k_sleep(K_SECONDS(5)); + + /* Stop advertising */ + err = bt_le_ext_adv_stop(adv); + if (err) { + TEST_FAIL("Failed to stop advertising (err %d)\n", err); + return; + } + + printk("Advertising stopped\n"); + + /* Delete advertising set */ + err = bt_le_ext_adv_delete(adv); + if (err) { + TEST_FAIL("Failed to delete advertising set (err %d)\n", err); + return; + } + + printk("Test passed\n"); + TEST_PASS("Decision broadcaster test passed"); +} + +static const struct bst_test_instance test_def[] = { + { + .test_id = "decision_broadcaster", + .test_descr = "Decision-based advertising broadcaster test", + .test_main_f = test_broadcaster_main, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_decision_broadcaster_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_def); +} + +bst_test_install_t test_installers[] = { + test_decision_broadcaster_install, + NULL, +}; + +int main(void) +{ + bst_main(); + return 0; +} diff --git a/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c b/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c new file mode 100644 index 0000000000000..3db5c184fc3a0 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/src/observer_decision.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Decision-Based Advertising Observer Test + * + * Test for extended scanning with decision-based advertising filtering. + * Reuses observer_decision sample code with babblekit integration. + */ + +#include +#include +#include +#include + +#include "babblekit/testcase.h" +#include "babblekit/flags.h" + +extern enum bst_result_t bst_result; + +#define NAME_LEN 30 +#define MIN_EXPECTED_REPORTS 3 + +DEFINE_FLAG_STATIC(flag_scan_started); +DEFINE_FLAG_STATIC(flag_adv_received); + +static int adv_report_count; + +static void scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + char name[NAME_LEN] = {0}; + uint8_t data_len; + uint8_t data_type; + bool found_decision_data = false; + + bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str)); + + printk("[DEVICE]: %s, RSSI %d, ", addr_str, info->rssi); + + /* Check for extended advertising */ + if (info->adv_type == BT_GAP_ADV_TYPE_EXT_ADV) { + printk("Extended Advertising "); + } + + /* Parse advertising data */ + while (buf->len > 1) { + data_len = net_buf_simple_pull_u8(buf); + if (data_len == 0) { + break; + } + + data_type = net_buf_simple_pull_u8(buf); + data_len--; + + switch (data_type) { + case BT_DATA_NAME_COMPLETE: + case BT_DATA_NAME_SHORTENED: + if (data_len < sizeof(name)) { + memcpy(name, buf->data, data_len); + name[data_len] = '\0'; + printk("Name: %s ", name); + } + net_buf_simple_pull(buf, data_len); + break; + + case BT_DATA_MANUFACTURER_DATA: + if (data_len >= 2) { + uint16_t company_id = sys_get_le16(buf->data); + printk("MFG: 0x%04x ", company_id); + + /* Check for our decision data */ + if (data_len >= 10) { + /* Check for "DECISION" string */ + if (memcmp(&buf->data[2], "DECISION", 8) == 0) { + printk("Data: DECISION "); + found_decision_data = true; + } + } + } + net_buf_simple_pull(buf, data_len); + break; + + default: + net_buf_simple_pull(buf, data_len); + break; + } + } + + printk("\n"); + + /* Count reports from our decision broadcaster */ + if (found_decision_data) { + adv_report_count++; + SET_FLAG(flag_adv_received); + printk("Decision advertising report received (count: %d)\n", adv_report_count); + } +} + +static struct bt_le_scan_cb scan_callbacks = { + .recv = scan_recv, +}; + +static void test_observer_main(void) +{ + struct bt_le_scan_param scan_param = { + .type = BT_LE_SCAN_TYPE_PASSIVE, + .options = BT_LE_SCAN_OPT_DECISION_BASED, + .interval = BT_GAP_SCAN_FAST_INTERVAL, + .window = BT_GAP_SCAN_FAST_WINDOW, + }; + int err; + + printk("Decision-Based Advertising Observer Test\n"); + + adv_report_count = 0; + + /* Initialize Bluetooth */ + err = bt_enable(NULL); + if (err) { + TEST_FAIL("Bluetooth init failed (err %d)\n", err); + return; + } + + printk("Bluetooth initialized\n"); + + /* Register scan callbacks */ + bt_le_scan_cb_register(&scan_callbacks); + + /* Set decision instructions for filtering */ + static const uint8_t decision_instructions[] = { + 0x01, /* Filter on device type */ + 0x02, /* Accept if capability matches */ + 0x00, /* No additional criteria */ + }; + + printk("Setting decision instructions\n"); + err = bt_le_scan_set_decision_instructions(decision_instructions, + sizeof(decision_instructions)); + if (err) { + TEST_FAIL("Failed to set decision instructions (err %d)\n", err); + return; + } + + printk("Decision instructions set (length: %d bytes)\n", + sizeof(decision_instructions)); + + /* Start scanning with decision-based filtering */ + printk("Starting scan with decision-based filtering\n"); + err = bt_le_scan_start(&scan_param, NULL); + if (err) { + TEST_FAIL("Scan start failed (err %d)\n", err); + return; + } + + printk("Scanning successfully started\n"); + SET_FLAG(flag_scan_started); + + /* Wait for advertising reports */ + printk("Waiting for advertising reports...\n"); + + /* Wait for at least one report or timeout */ + for (int i = 0; i < 8 && !TEST_FLAG(flag_adv_received); i++) { + k_sleep(K_SECONDS(1)); + } + + if (!TEST_FLAG(flag_adv_received)) { + TEST_FAIL("No advertising reports received\n"); + return; + } + + /* Continue scanning for more reports */ + k_sleep(K_SECONDS(3)); + + /* Stop scanning */ + err = bt_le_scan_stop(); + if (err) { + TEST_FAIL("Scan stop failed (err %d)\n", err); + return; + } + + printk("Scanning stopped\n"); + printk("Total advertising reports received: %d\n", adv_report_count); + + /* Verify we received multiple reports */ + if (adv_report_count < MIN_EXPECTED_REPORTS) { + TEST_FAIL("Too few advertising reports (%d < %d)\n", + adv_report_count, MIN_EXPECTED_REPORTS); + return; + } + + printk("Test passed\n"); + TEST_PASS("Decision observer test passed"); +} + +static const struct bst_test_instance test_def[] = { + { + .test_id = "decision_observer", + .test_descr = "Decision-based advertising observer test", + .test_main_f = test_observer_main, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_decision_observer_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_def); +} + +bst_test_install_t test_installers[] = { + test_decision_observer_install, + NULL, +}; + +int main(void) +{ + bst_main(); + return 0; +} diff --git a/tests/bsim/bluetooth/host/adv/decision/testcase.yaml b/tests/bsim/bluetooth/host/adv/decision/testcase.yaml new file mode 100644 index 0000000000000..a9dfba1056b20 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/testcase.yaml @@ -0,0 +1,20 @@ +common: + build_only: true + tags: + - bluetooth + platform_allow: + - nrf52_bsim/native + - nrf5340bsim/nrf5340/cpunet + harness: bsim + +tests: + bluetooth.host.adv.decision.broadcaster: + harness_config: + bsim_exe_name: tests_bsim_bluetooth_host_adv_decision_prj_broadcaster_conf + extra_args: + CONF_FILE=prj_broadcaster.conf + bluetooth.host.adv.decision.observer: + harness_config: + bsim_exe_name: tests_bsim_bluetooth_host_adv_decision_prj_observer_conf + extra_args: + CONF_FILE=prj_observer.conf diff --git a/tests/bsim/bluetooth/host/adv/decision/tests_scripts/run.sh b/tests/bsim/bluetooth/host/adv/decision/tests_scripts/run.sh new file mode 100755 index 0000000000000..13ae7f8ed05f5 --- /dev/null +++ b/tests/bsim/bluetooth/host/adv/decision/tests_scripts/run.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Decision-based advertising filtering test: +# +# - A Bluetooth LE broadcaster advertises with extended advertising and +# decision-based advertising filtering support, and an observer scans +# with decision-based filtering enabled. + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +simulation_id="decision_adv" +verbosity_level=2 + +cd ${BSIM_OUT_PATH}/bin + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_host_adv_decision_prj_broadcaster_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=0 -RealEncryption=0 \ + -testid=decision_broadcaster -rs=23 + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_host_adv_decision_prj_observer_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=1 -RealEncryption=0 \ + -testid=decision_observer -rs=6 + +Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \ + -D=2 -sim_length=10e6 $@ + +wait_for_background_jobs