diff --git a/include/zephyr/bluetooth/bluetooth.h b/include/zephyr/bluetooth/bluetooth.h index 943dedb0260..1523a4f5928 100644 --- a/include/zephyr/bluetooth/bluetooth.h +++ b/include/zephyr/bluetooth/bluetooth.h @@ -2294,6 +2294,7 @@ BUILD_ASSERT(BT_GAP_SCAN_FAST_WINDOW == BT_GAP_SCAN_FAST_INTERVAL_MIN, * * @return Zero on success or error code otherwise, positive in case of * protocol error or negative (POSIX) in case of stack internal error. + * @retval -EBUSY if the scanner is already being started in a different thread. */ int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb); diff --git a/subsys/bluetooth/host/conn.c b/subsys/bluetooth/host/conn.c index b58bfae0032..e11375b196c 100644 --- a/subsys/bluetooth/host/conn.c +++ b/subsys/bluetooth/host/conn.c @@ -35,6 +35,7 @@ #include "hci_core.h" #include "id.h" #include "adv.h" +#include "scan.h" #include "conn_internal.h" #include "l2cap_internal.h" #include "keys.h" @@ -1265,10 +1266,18 @@ void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state) * the application through bt_conn_disconnect or by * timeout set by bt_conn_le_create_param.timeout. */ - if (conn->err) { - notify_connected(conn); - } + if (IS_ENABLED(CONFIG_BT_CENTRAL)) { + int err = bt_le_scan_user_remove(BT_LE_SCAN_USER_CONN); + if (err) { + LOG_WRN("Error while removing conn user from scanner (%d)", + err); + } + + if (conn->err) { + notify_connected(conn); + } + } bt_conn_unref(conn); break; case BT_CONN_ADV_DIR_CONNECTABLE: @@ -1693,7 +1702,7 @@ int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason) conn->err = reason; bt_conn_set_state(conn, BT_CONN_DISCONNECTED); if (IS_ENABLED(CONFIG_BT_CENTRAL)) { - bt_le_scan_update(false); + return bt_le_scan_user_add(BT_LE_SCAN_USER_CONN); } return 0; case BT_CONN_INITIATING: @@ -3360,27 +3369,32 @@ static int conn_le_create_common_checks(const bt_addr_le_t *peer, { if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) { + LOG_DBG("Conn check failed: BT dev not ready."); return -EAGAIN; } if (!bt_le_conn_params_valid(conn_param)) { + LOG_DBG("Conn check failed: invalid parameters."); return -EINVAL; } - if (!BT_LE_STATES_SCAN_INIT(bt_dev.le.states) && - atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { + if (!BT_LE_STATES_SCAN_INIT(bt_dev.le.states) && bt_le_explicit_scanner_running()) { + LOG_DBG("Conn check failed: scanner was explicitly requested."); return -EAGAIN; } if (atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) { + LOG_DBG("Conn check failed: device is already initiating."); return -EALREADY; } if (!bt_id_scan_random_addr_check()) { + LOG_DBG("Conn check failed: invalid random address."); return -EINVAL; } if (bt_conn_exists_le(BT_ID_DEFAULT, peer)) { + LOG_DBG("Conn check failed: ACL connection already exists."); return -EINVAL; } @@ -3433,8 +3447,9 @@ int bt_conn_le_create(const bt_addr_le_t *peer, const struct bt_conn_le_create_p /* Use host-based identity resolving. */ bt_conn_set_state(conn, BT_CONN_SCAN_BEFORE_INITIATING); - err = bt_le_scan_update(true); + err = bt_le_scan_user_add(BT_LE_SCAN_USER_CONN); if (err) { + bt_le_scan_user_remove(BT_LE_SCAN_USER_CONN); bt_conn_set_state(conn, BT_CONN_DISCONNECTED); bt_conn_unref(conn); @@ -3454,7 +3469,12 @@ int bt_conn_le_create(const bt_addr_le_t *peer, const struct bt_conn_le_create_p bt_conn_set_state(conn, BT_CONN_DISCONNECTED); bt_conn_unref(conn); - bt_le_scan_update(false); + /* Best-effort attempt to inform the scanner that the initiator stopped. */ + int scan_check_err = bt_le_scan_user_add(BT_LE_SCAN_USER_NONE); + + if (scan_check_err) { + LOG_WRN("Error while updating the scanner (%d)", scan_check_err); + } return err; } @@ -3556,17 +3576,18 @@ int bt_le_set_auto_conn(const bt_addr_le_t *addr, } } + int err = 0; if (conn->state == BT_CONN_DISCONNECTED && atomic_test_bit(bt_dev.flags, BT_DEV_READY)) { if (param) { bt_conn_set_state(conn, BT_CONN_SCAN_BEFORE_INITIATING); + err = bt_le_scan_user_add(BT_LE_SCAN_USER_CONN); } - bt_le_scan_update(false); } bt_conn_unref(conn); - return 0; + return err; } #endif /* !defined(CONFIG_BT_FILTER_ACCEPT_LIST) */ #endif /* CONFIG_BT_CENTRAL */ diff --git a/subsys/bluetooth/host/hci_core.c b/subsys/bluetooth/host/hci_core.c index 46cb4254313..427e0c18acd 100644 --- a/subsys/bluetooth/host/hci_core.c +++ b/subsys/bluetooth/host/hci_core.c @@ -1010,7 +1010,12 @@ static void hci_disconn_complete(struct net_buf *buf) #if defined(CONFIG_BT_CENTRAL) && !defined(CONFIG_BT_FILTER_ACCEPT_LIST) if (atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT)) { bt_conn_set_state(conn, BT_CONN_SCAN_BEFORE_INITIATING); - bt_le_scan_update(false); + /* Just a best-effort check if the scanner should be started. */ + int err = bt_le_scan_user_remove(BT_LE_SCAN_USER_NONE); + + if (err) { + LOG_WRN("Error while updating the scanner (%d)", err); + } } #endif /* defined(CONFIG_BT_CENTRAL) && !defined(CONFIG_BT_FILTER_ACCEPT_LIST) */ @@ -1561,9 +1566,14 @@ void bt_hci_le_enh_conn_complete(struct bt_hci_evt_le_enh_conn_complete *evt) bt_conn_unref(conn); - if (IS_ENABLED(CONFIG_BT_CENTRAL) && - conn->role == BT_HCI_ROLE_CENTRAL) { - bt_le_scan_update(false); + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_HCI_ROLE_CENTRAL) { + int err; + + /* Just a best-effort check if the scanner should be started. */ + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_NONE); + if (err) { + LOG_WRN("Error while updating the scanner (%d)", err); + } } } @@ -1661,7 +1671,11 @@ static void enh_conn_complete_error_handle(uint8_t status) if (IS_ENABLED(CONFIG_BT_CENTRAL) && status == BT_HCI_ERR_UNKNOWN_CONN_ID) { le_conn_complete_cancel(status); - bt_le_scan_update(false); + int err = bt_le_scan_user_remove(BT_LE_SCAN_USER_NONE); + + if (err) { + LOG_WRN("Error while updating the scanner (%d)", err); + } return; } @@ -4200,7 +4214,7 @@ void bt_finalize_init(void) atomic_set_bit(bt_dev.flags, BT_DEV_READY); if (IS_ENABLED(CONFIG_BT_OBSERVER)) { - bt_le_scan_update(false); + bt_scan_reset(); } bt_dev_show_info(); diff --git a/subsys/bluetooth/host/hci_core.h b/subsys/bluetooth/host/hci_core.h index 827d7a4b919..4f632dd9f31 100644 --- a/subsys/bluetooth/host/hci_core.h +++ b/subsys/bluetooth/host/hci_core.h @@ -38,16 +38,11 @@ enum { BT_DEV_HAS_PUB_KEY, BT_DEV_PUB_KEY_BUSY, - /** The application explicitly instructed the stack to scan for advertisers - * using the API @ref bt_le_scan_start(). - */ - BT_DEV_EXPLICIT_SCAN, - /** The application either explicitly or implicitly instructed the stack to scan * for advertisers. * * Examples of such cases - * - Explicit scanning, @ref BT_DEV_EXPLICIT_SCAN. + * - Explicit scanning, @ref BT_LE_SCAN_USER_EXPLICIT_SCAN. * - The application instructed the stack to automatically connect if a given device * is detected. * - The application wants to connect to a peer device using private addresses, but @@ -63,13 +58,9 @@ enum { */ BT_DEV_SCANNING, - /* Cached parameters used when initially enabling the scanner. - * These are needed to ensure the same parameters are used when restarting - * the scanner after refreshing an RPA. + /** + * Scanner is configured with a timeout. */ - BT_DEV_ACTIVE_SCAN, - BT_DEV_SCAN_FILTER_DUP, - BT_DEV_SCAN_FILTERED, BT_DEV_SCAN_LIMITED, BT_DEV_INITIATING, @@ -489,30 +480,6 @@ uint8_t bt_get_phy(uint8_t hci_phy); */ int bt_get_df_cte_type(uint8_t hci_cte_type); -/** Start or restart scanner if needed - * - * Examples of cases where it may be required to start/restart a scanner: - * - When the auto-connection establishement feature is used: - * - When the host sets a connection context for auto-connection establishment. - * - When a connection was established. - * The host may now be able to retry to automatically set up a connection. - * - When a connection was disconnected/lost. - * The host may now be able to retry to automatically set up a connection. - * - When the application stops explicit scanning. - * The host may now be able to retry to automatically set up a connection. - * - The application tries to connect to another device, but fails. - * The host may now be able to retry to automatically set up a connection. - * - When the application wants to connect to a device, but we need - * to fallback to host privacy. - * - When the application wants to establish a periodic sync to a device - * and the application has not already started scanning. - * - * @param fast_scan Use fast scan parameters or slow scan parameters - * - * @return 0 in case of success, or a negative error code on failure. - */ -int bt_le_scan_update(bool fast_scan); - int bt_le_create_conn(const struct bt_conn *conn); int bt_le_create_conn_cancel(void); int bt_le_create_conn_synced(const struct bt_conn *conn, const struct bt_le_ext_adv *adv, diff --git a/subsys/bluetooth/host/id.c b/subsys/bluetooth/host/id.c index e4d48ce464d..871e016f581 100644 --- a/subsys/bluetooth/host/id.c +++ b/subsys/bluetooth/host/id.c @@ -689,11 +689,11 @@ static void rpa_timeout(struct k_work *work) le_rpa_invalidate(); /* IF no roles using the RPA is running we can stop the RPA timer */ - if (!(adv_enabled || - atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING) || - (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING) && - atomic_test_bit(bt_dev.flags, BT_DEV_ACTIVE_SCAN)))) { - return; + if (IS_ENABLED(CONFIG_BT_CENTRAL)) { + if (!(adv_enabled || atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING) || + bt_le_scan_active_scanner_running())) { + return; + } } le_update_private_addr(); diff --git a/subsys/bluetooth/host/scan.c b/subsys/bluetooth/host/scan.c index 35f09977699..ad469a57588 100644 --- a/subsys/bluetooth/host/scan.c +++ b/subsys/bluetooth/host/scan.c @@ -5,8 +5,12 @@ * SPDX-License-Identifier: Apache-2.0 */ #include +#include +#include +#include #include +#include #include #include @@ -25,14 +29,32 @@ #include "id.h" #include "common/bt_str.h" +#include "scan.h" #define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL #include LOG_MODULE_REGISTER(bt_scan); +struct scanner_state { + ATOMIC_DEFINE(scan_flags, BT_LE_SCAN_USER_NUM_FLAGS); + struct bt_le_scan_param explicit_scan_param; + struct bt_le_scan_param used_scan_param; + struct k_mutex scan_update_mutex; + struct k_mutex scan_explicit_params_mutex; +}; + +enum scan_action { + SCAN_ACTION_NONE, + SCAN_ACTION_START, + SCAN_ACTION_STOP, + SCAN_ACTION_UPDATE, +}; + static bt_le_scan_cb_t *scan_dev_found_cb; static sys_slist_t scan_cbs = SYS_SLIST_STATIC_INIT(&scan_cbs); +static struct scanner_state scan_state; + #if defined(CONFIG_BT_EXT_ADV) /* A buffer used to reassemble advertisement data from the controller. */ NET_BUF_SIMPLE_DEFINE(ext_scan_buf, CONFIG_BT_EXT_SCAN_BUF_SIZE); @@ -77,7 +99,7 @@ static sys_slist_t pa_sync_cbs = SYS_SLIST_STATIC_INIT(&pa_sync_cbs); #endif /* defined(CONFIG_BT_PER_ADV_SYNC) */ #endif /* defined(CONFIG_BT_EXT_ADV) */ -void bt_scan_reset(void) +void bt_scan_softreset(void) { scan_dev_found_cb = NULL; #if defined(CONFIG_BT_EXT_ADV) @@ -85,7 +107,15 @@ void bt_scan_reset(void) #endif } -static int set_le_ext_scan_enable(uint8_t enable, uint16_t duration) +void bt_scan_reset(void) +{ + memset(&scan_state, 0x0, sizeof(scan_state)); + k_mutex_init(&scan_state.scan_update_mutex); + k_mutex_init(&scan_state.scan_explicit_params_mutex); + bt_scan_softreset(); +} + +static int cmd_le_set_ext_scan_enable(bool enable, bool filter_duplicates, uint16_t duration) { struct bt_hci_cp_le_set_ext_scan_enable *cp; struct bt_hci_cmd_state_set state; @@ -99,13 +129,7 @@ static int set_le_ext_scan_enable(uint8_t enable, uint16_t duration) cp = net_buf_add(buf, sizeof(*cp)); - if (enable == BT_HCI_LE_SCAN_ENABLE) { - cp->filter_dup = atomic_test_bit(bt_dev.flags, - BT_DEV_SCAN_FILTER_DUP); - } else { - cp->filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_DISABLE; - } - + cp->filter_dup = filter_duplicates; cp->enable = enable; cp->duration = sys_cpu_to_le16(duration); cp->period = 0; @@ -121,7 +145,7 @@ static int set_le_ext_scan_enable(uint8_t enable, uint16_t duration) return 0; } -static int bt_le_scan_set_enable_legacy(uint8_t enable) +static int cmd_le_set_scan_enable_legacy(bool enable, bool filter_duplicates) { struct bt_hci_cp_le_set_scan_enable *cp; struct bt_hci_cmd_state_set state; @@ -135,13 +159,7 @@ static int bt_le_scan_set_enable_legacy(uint8_t enable) cp = net_buf_add(buf, sizeof(*cp)); - if (enable == BT_HCI_LE_SCAN_ENABLE) { - cp->filter_dup = atomic_test_bit(bt_dev.flags, - BT_DEV_SCAN_FILTER_DUP); - } else { - cp->filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_DISABLE; - } - + cp->filter_dup = filter_duplicates; cp->enable = enable; bt_hci_cmd_state_set_init(buf, &state, bt_dev.flags, BT_DEV_SCANNING, @@ -155,20 +173,49 @@ static int bt_le_scan_set_enable_legacy(uint8_t enable) return 0; } -int bt_le_scan_set_enable(uint8_t enable) +static int cmd_le_set_scan_enable(bool enable, bool filter_duplicates) { - if (IS_ENABLED(CONFIG_BT_EXT_ADV) && - BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) { - return set_le_ext_scan_enable(enable, 0); + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) { + return cmd_le_set_ext_scan_enable(enable, filter_duplicates, 0); } - return bt_le_scan_set_enable_legacy(enable); + return cmd_le_set_scan_enable_legacy(enable, filter_duplicates); } -static int start_le_scan_ext(struct bt_hci_ext_scan_phy *phy_1m, - struct bt_hci_ext_scan_phy *phy_coded, - uint16_t duration) +int bt_le_scan_set_enable(uint8_t enable) { + return cmd_le_set_scan_enable(enable, scan_state.used_scan_param.options & + BT_LE_SCAN_OPT_FILTER_DUPLICATE); +} + +static int start_le_scan_ext(struct bt_le_scan_param *scan_param) +{ + struct bt_hci_ext_scan_phy param_1m; + struct bt_hci_ext_scan_phy param_coded; + + struct bt_hci_ext_scan_phy *phy_1m = NULL; + struct bt_hci_ext_scan_phy *phy_coded = NULL; + + if (!(scan_param->options & BT_LE_SCAN_OPT_NO_1M)) { + param_1m.type = scan_param->type; + param_1m.interval = sys_cpu_to_le16(scan_param->interval); + param_1m.window = sys_cpu_to_le16(scan_param->window); + + phy_1m = ¶m_1m; + } + + if (scan_param->options & BT_LE_SCAN_OPT_CODED) { + uint16_t interval = scan_param->interval_coded ? scan_param->interval_coded + : scan_param->interval; + uint16_t window = + scan_param->window_coded ? scan_param->window_coded : scan_param->window; + + param_coded.type = scan_param->type; + param_coded.interval = sys_cpu_to_le16(interval); + param_coded.window = sys_cpu_to_le16(window); + phy_coded = ¶m_coded; + } + struct bt_hci_cp_le_set_ext_scan_param *set_param; struct net_buf *buf; uint8_t own_addr_type; @@ -178,7 +225,7 @@ static int start_le_scan_ext(struct bt_hci_ext_scan_phy *phy_1m, active_scan = (phy_1m && phy_1m->type == BT_HCI_LE_SCAN_ACTIVE) || (phy_coded && phy_coded->type == BT_HCI_LE_SCAN_ACTIVE); - if (duration > 0) { + if (scan_param->timeout > 0) { atomic_set_bit(bt_dev.flags, BT_DEV_SCAN_LIMITED); /* Allow bt_le_oob_get_local to be called directly before @@ -205,13 +252,9 @@ static int start_le_scan_ext(struct bt_hci_ext_scan_phy *phy_1m, set_param = net_buf_add(buf, sizeof(*set_param)); set_param->own_addr_type = own_addr_type; set_param->phys = 0; - - if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && - atomic_test_bit(bt_dev.flags, BT_DEV_SCAN_FILTERED)) { - 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_param->options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST + ? BT_HCI_LE_SCAN_FP_BASIC_FILTER + : BT_HCI_LE_SCAN_FP_BASIC_NO_FILTER; if (phy_1m) { set_param->phys |= BT_HCI_LE_EXT_SCAN_PHY_1M; @@ -228,17 +271,17 @@ static int start_le_scan_ext(struct bt_hci_ext_scan_phy *phy_1m, return err; } - err = set_le_ext_scan_enable(BT_HCI_LE_SCAN_ENABLE, duration); + err = cmd_le_set_ext_scan_enable(BT_HCI_LE_SCAN_ENABLE, + scan_param->options & BT_LE_SCAN_OPT_FILTER_DUPLICATE, + scan_param->timeout); if (err) { return err; } - atomic_set_bit_to(bt_dev.flags, BT_DEV_ACTIVE_SCAN, active_scan); - return 0; } -static int start_le_scan_legacy(uint8_t scan_type, uint16_t interval, uint16_t window) +static int start_le_scan_legacy(struct bt_le_scan_param *param) { struct bt_hci_cp_le_set_scan_param set_param; struct net_buf *buf; @@ -247,22 +290,22 @@ static int start_le_scan_legacy(uint8_t scan_type, uint16_t interval, uint16_t w (void)memset(&set_param, 0, sizeof(set_param)); - set_param.scan_type = scan_type; + set_param.scan_type = param->type; /* for the rest parameters apply default values according to * spec 4.2, vol2, part E, 7.8.10 */ - set_param.interval = sys_cpu_to_le16(interval); - set_param.window = sys_cpu_to_le16(window); + 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) && - atomic_test_bit(bt_dev.flags, BT_DEV_SCAN_FILTERED)) { + 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; } - active_scan = scan_type == BT_HCI_LE_SCAN_ACTIVE; + active_scan = param->type == BT_HCI_LE_SCAN_ACTIVE; err = bt_id_set_scan_own_addr(active_scan, &set_param.addr_type); if (err) { return err; @@ -280,99 +323,205 @@ static int start_le_scan_legacy(uint8_t scan_type, uint16_t interval, uint16_t w return err; } - err = bt_le_scan_set_enable(BT_HCI_LE_SCAN_ENABLE); + err = cmd_le_set_scan_enable(BT_HCI_LE_SCAN_ENABLE, + param->options & BT_LE_SCAN_OPT_FILTER_DUPLICATE); if (err) { return err; } - atomic_set_bit_to(bt_dev.flags, BT_DEV_ACTIVE_SCAN, active_scan); + return 0; +} + +bool bt_le_scan_active_scanner_running(void) +{ + return atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING) && + scan_state.used_scan_param.type == BT_LE_SCAN_TYPE_ACTIVE; +} + +static void select_scan_params(struct bt_le_scan_param *scan_param) +{ + /* From high priority to low priority: select parameters */ + /* 1. Priority: explicitly chosen parameters */ + if (atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_EXPLICIT_SCAN)) { + memcpy(scan_param, &scan_state.explicit_scan_param, sizeof(*scan_param)); + } + /* Below this, the scanner module chooses the parameters. */ + /* 2. Priority: reuse parameters from initiator */ + else if (atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) { + *scan_param = (struct bt_le_scan_param){ + .type = BT_LE_SCAN_TYPE_PASSIVE, + .options = BT_LE_SCAN_OPT_FILTER_DUPLICATE, + .interval = bt_dev.create_param.interval, + .window = bt_dev.create_param.window, + .timeout = 0, + .interval_coded = bt_dev.create_param.interval_coded, + .window_coded = bt_dev.create_param.window_coded, + }; + } + /* 3. Priority: choose custom parameters */ + else { + *scan_param = (struct bt_le_scan_param){ + .type = BT_LE_SCAN_TYPE_PASSIVE, + .options = BT_LE_SCAN_OPT_FILTER_DUPLICATE, + .interval = CONFIG_BT_BACKGROUND_SCAN_INTERVAL, + .window = CONFIG_BT_BACKGROUND_SCAN_WINDOW, + .timeout = 0, + .interval_coded = 0, + .window_coded = 0, + }; + + if (BT_FEAT_LE_PHY_CODED(bt_dev.le.features)) { + scan_param->options |= BT_LE_SCAN_OPT_CODED; + } + + if (atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_PER_SYNC) || + atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_CONN)) { + scan_param->window = BT_GAP_SCAN_FAST_WINDOW; + scan_param->interval = BT_GAP_SCAN_FAST_INTERVAL; + } + } +} + +static int start_scan(struct bt_le_scan_param *scan_param) +{ + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) { + return start_le_scan_ext(scan_param); + } + + return start_le_scan_legacy(scan_param); +} + +static bool is_already_using_same_params(struct bt_le_scan_param *scan_param) +{ + return !memcmp(scan_param, &scan_state.used_scan_param, sizeof(*scan_param)); +} + +static enum scan_action get_scan_action(struct bt_le_scan_param *scan_param) +{ + bool is_scanning = atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING); + + /* Check if there is reason to have the scanner running */ + if (atomic_get(scan_state.scan_flags) != 0) { + if (is_scanning) { + if (is_already_using_same_params(scan_param)) { + /* Already scanning with the desired parameters */ + return SCAN_ACTION_NONE; + } else { + return SCAN_ACTION_UPDATE; + } + } else { + return SCAN_ACTION_START; + } + } else { + /* Scanner should not run */ + if (is_scanning) { + return SCAN_ACTION_STOP; + } else { + return SCAN_ACTION_NONE; + } + } +} + +static int scan_update(void) +{ + int32_t err; + + struct bt_le_scan_param scan_param; + + /* Prevent partial updates of the scanner state. */ + err = k_mutex_lock(&scan_state.scan_update_mutex, K_NO_WAIT); + + if (err) { + return err; + } + + select_scan_params(&scan_param); + + enum scan_action action = get_scan_action(&scan_param); + + /* start/stop/update if required and allowed */ + switch (action) { + case SCAN_ACTION_NONE: + break; + case SCAN_ACTION_STOP: + err = cmd_le_set_scan_enable(BT_HCI_LE_SCAN_DISABLE, + BT_HCI_LE_SCAN_FILTER_DUP_DISABLE); + if (err) { + LOG_DBG("Could not stop scanner: %d", err); + break; + } + memset(&scan_state.used_scan_param, 0x0, + sizeof(scan_state.used_scan_param)); + break; + case SCAN_ACTION_UPDATE: + err = cmd_le_set_scan_enable(BT_HCI_LE_SCAN_DISABLE, + BT_HCI_LE_SCAN_FILTER_DUP_DISABLE); + if (err) { + LOG_DBG("Could not stop scanner to update: %d", err); + break; + } + __fallthrough; + case SCAN_ACTION_START: + err = start_scan(&scan_param); + if (err) { + LOG_DBG("Could not start scanner: %d", err); + break; + } + memcpy(&scan_state.used_scan_param, &scan_param, sizeof(scan_param)); + break; + } + + k_mutex_unlock(&scan_state.scan_update_mutex); + + return err; +} + +static uint32_t scan_check_if_state_allowed(enum bt_le_scan_user flag) +{ + /* check if state is already set */ + if (atomic_test_bit(scan_state.scan_flags, flag)) { + return -EALREADY; + } + + if (flag == BT_LE_SCAN_USER_EXPLICIT_SCAN && !BT_LE_STATES_SCAN_INIT(bt_dev.le.states) && + atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) { + return -EPERM; + } return 0; } -static int start_host_initiated_scan(bool fast_scan) +int bt_le_scan_user_add(enum bt_le_scan_user flag) { - uint16_t interval, window; + uint32_t err; - if (fast_scan) { - interval = BT_GAP_SCAN_FAST_INTERVAL; - window = BT_GAP_SCAN_FAST_WINDOW; - } else { - interval = CONFIG_BT_BACKGROUND_SCAN_INTERVAL; - window = CONFIG_BT_BACKGROUND_SCAN_WINDOW; - } - - if (IS_ENABLED(CONFIG_BT_EXT_ADV) && - BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) { - struct bt_hci_ext_scan_phy scan_phy_params; - - scan_phy_params.type = BT_HCI_LE_SCAN_PASSIVE; - scan_phy_params.interval = sys_cpu_to_le16(interval); - scan_phy_params.window = sys_cpu_to_le16(window); - - /* Scan on 1M + Coded if the controller supports it*/ - if (BT_FEAT_LE_PHY_CODED(bt_dev.le.features)) { - return start_le_scan_ext(&scan_phy_params, &scan_phy_params, 0); - } else { - return start_le_scan_ext(&scan_phy_params, NULL, 0); - } - - } - - return start_le_scan_legacy(BT_HCI_LE_SCAN_PASSIVE, interval, window); -} - -int bt_le_scan_update(bool fast_scan) -{ - if (atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { - /* The application has already explicitly started scanning. - * We should keep the scanner running to avoid changing scan parameters. + if (flag == BT_LE_SCAN_USER_NONE) { + /* Only check if the scanner parameters should be updated / the scanner should be + * started. This is mainly triggered once connections are established. */ - return 0; - } - - if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) { - int err; - - err = bt_le_scan_set_enable(BT_HCI_LE_SCAN_DISABLE); + } else { + /* Check if it can be enabled */ + err = scan_check_if_state_allowed(flag); if (err) { return err; } + atomic_set_bit(scan_state.scan_flags, flag); } - if (IS_ENABLED(CONFIG_BT_CENTRAL)) { - struct bt_conn *conn; + return scan_update(); +} - if (!BT_LE_STATES_SCAN_INIT(bt_dev.le.states)) { - /* don't restart scan if we have pending connection */ - conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, NULL, - BT_CONN_INITIATING); - if (conn) { - bt_conn_unref(conn); - return 0; - } - } - - conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, NULL, - BT_CONN_SCAN_BEFORE_INITIATING); - if (conn) { - atomic_set_bit(bt_dev.flags, BT_DEV_SCAN_FILTER_DUP); - - bt_conn_unref(conn); - - /* Start/Restart the scanner */ - return start_host_initiated_scan(fast_scan); - } +int bt_le_scan_user_remove(enum bt_le_scan_user flag) +{ + if (flag == BT_LE_SCAN_USER_NONE) { + /* Only check if the scanner parameters should be updated / the scanner should be + * started. This is mainly triggered once connections are established. + */ + } else { + atomic_clear_bit(scan_state.scan_flags, flag); } -#if defined(CONFIG_BT_PER_ADV_SYNC) - if (get_pending_per_adv_sync()) { - /* Start/Restart the scanner. */ - return start_host_initiated_scan(fast_scan); - } -#endif - - return 0; + return scan_update(); } #if defined(CONFIG_BT_CENTRAL) @@ -380,12 +529,13 @@ static void check_pending_conn(const bt_addr_le_t *id_addr, const bt_addr_le_t *addr, uint8_t adv_props) { struct bt_conn *conn; + int err; /* No connections are allowed during explicit scanning * when the controller does not support concurrent scanning and initiating. */ if (!BT_LE_STATES_SCAN_INIT(bt_dev.le.states) && - atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { + atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_EXPLICIT_SCAN)) { return; } @@ -400,11 +550,13 @@ static void check_pending_conn(const bt_addr_le_t *id_addr, return; } - if (!BT_LE_STATES_SCAN_INIT(bt_dev.le.states)) { - if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING) && - bt_le_scan_set_enable(BT_HCI_LE_SCAN_DISABLE)) { - goto failed; - } + /* Stop the scanner if there is no other reason to have it running. + * Ignore possible failures here, since the user is guaranteed to be removed + * and the scanner state is updated once the initiator starts / stops. + */ + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_CONN); + if (err) { + LOG_DBG("Error while removing conn user from scanner (%d)", err); } bt_addr_le_copy(&conn->le.resp_addr, addr); @@ -420,7 +572,12 @@ failed: conn->err = BT_HCI_ERR_UNSPECIFIED; bt_conn_set_state(conn, BT_CONN_DISCONNECTED); bt_conn_unref(conn); - bt_le_scan_update(false); + /* Just a best-effort check if the scanner should be started. */ + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_NONE); + + if (err) { + LOG_WRN("Error while updating the scanner (%d)", err); + } } #endif /* CONFIG_BT_CENTRAL */ @@ -465,9 +622,8 @@ static void le_adv_recv(bt_addr_le_t *addr, struct bt_le_scan_recv_info *info, LOG_DBG("%s event %u, len %u, rssi %d dBm", bt_addr_le_str(addr), info->adv_type, len, info->rssi); - if (!IS_ENABLED(CONFIG_BT_PRIVACY) && - !IS_ENABLED(CONFIG_BT_SCAN_WITH_IDENTITY) && - atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN) && + if (!IS_ENABLED(CONFIG_BT_PRIVACY) && !IS_ENABLED(CONFIG_BT_SCAN_WITH_IDENTITY) && + atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_EXPLICIT_SCAN) && (info->adv_props & BT_HCI_LE_ADV_PROP_DIRECT)) { LOG_DBG("Dropped direct adv report"); return; @@ -517,8 +673,16 @@ void bt_hci_le_scan_timeout(struct net_buf *buf) { struct bt_le_scan_cb *listener, *next; - atomic_clear_bit(bt_dev.flags, BT_DEV_SCANNING); - atomic_clear_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN); + int err = bt_le_scan_user_remove(BT_LE_SCAN_USER_EXPLICIT_SCAN); + + if (err) { + k_yield(); + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_EXPLICIT_SCAN); + } + + if (err) { + LOG_WRN("Could not stop the explicit scanner (%d)", err); + } atomic_clear_bit(bt_dev.flags, BT_DEV_SCAN_LIMITED); atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID); @@ -610,7 +774,7 @@ void bt_hci_le_adv_ext_report(struct net_buf *buf) bool more_to_come; bool is_new_advertiser; - if (!atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { + if (!atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_EXPLICIT_SCAN)) { /* The application has not requested explicit scan, so it is not expecting * advertising reports. Discard, and reset the reassembler if not inactive * This is done in the loop as this flag can change between each iteration, @@ -1014,9 +1178,8 @@ static void bt_hci_le_per_adv_sync_established_common(struct net_buf *buf) pending_per_adv_sync = get_pending_per_adv_sync(); if (pending_per_adv_sync) { - atomic_clear_bit(pending_per_adv_sync->flags, - BT_PER_ADV_SYNC_SYNCING); - err = bt_le_scan_update(false); + atomic_clear_bit(pending_per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCING); + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_PER_SYNC); if (err) { LOG_ERR("Could not update scan (%d)", err); @@ -1474,7 +1637,7 @@ void bt_hci_le_adv_report(struct net_buf *buf) while (num_reports--) { struct bt_le_scan_recv_info adv_info; - if (!atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { + if (!atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_EXPLICIT_SCAN)) { /* The application has not requested explicit scan, so it is not expecting * advertising reports. Discard. * This is done in the loop as this flag can change between each iteration, @@ -1577,91 +1740,37 @@ int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb) return -EINVAL; } - /* Return if active scan is already enabled */ - if (atomic_test_and_set_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { - return -EALREADY; - } - - if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) { - err = bt_le_scan_set_enable(BT_HCI_LE_SCAN_DISABLE); - if (err) { - atomic_clear_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN); - return err; - } - } - - atomic_set_bit_to(bt_dev.flags, BT_DEV_SCAN_FILTER_DUP, - param->options & BT_LE_SCAN_OPT_FILTER_DUPLICATE); - -#if defined(CONFIG_BT_FILTER_ACCEPT_LIST) - atomic_set_bit_to(bt_dev.flags, BT_DEV_SCAN_FILTERED, - param->options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST); -#endif /* defined(CONFIG_BT_FILTER_ACCEPT_LIST) */ - - if (IS_ENABLED(CONFIG_BT_EXT_ADV) && - BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) { - if (IS_ENABLED(CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL) && param->timeout) { - atomic_clear_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN); - return -ENOTSUP; - } - - struct bt_hci_ext_scan_phy param_1m; - struct bt_hci_ext_scan_phy param_coded; - - struct bt_hci_ext_scan_phy *phy_1m = NULL; - struct bt_hci_ext_scan_phy *phy_coded = NULL; - - if (!(param->options & BT_LE_SCAN_OPT_NO_1M)) { - param_1m.type = param->type; - param_1m.interval = sys_cpu_to_le16(param->interval); - param_1m.window = sys_cpu_to_le16(param->window); - - phy_1m = ¶m_1m; - } - - if (param->options & BT_LE_SCAN_OPT_CODED) { - uint16_t interval = param->interval_coded ? - param->interval_coded : - param->interval; - uint16_t window = param->window_coded ? - param->window_coded : - param->window; - - param_coded.type = param->type; - param_coded.interval = sys_cpu_to_le16(interval); - param_coded.window = sys_cpu_to_le16(window); - phy_coded = ¶m_coded; - } - - err = start_le_scan_ext(phy_1m, phy_coded, param->timeout); - } else { - if (param->timeout) { - atomic_clear_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN); - return -ENOTSUP; - } - - err = start_le_scan_legacy(param->type, param->interval, - param->window); - } + /* Prevent multiple threads to try to enable explicit scanning at the same time. + * That could lead to unwanted overwriting of scan_state.explicit_scan_param. + */ + err = k_mutex_lock(&scan_state.scan_explicit_params_mutex, K_NO_WAIT); if (err) { - atomic_clear_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN); return err; } - scan_dev_found_cb = cb; + err = scan_check_if_state_allowed(BT_LE_SCAN_USER_EXPLICIT_SCAN); - return 0; + if (err) { + k_mutex_unlock(&scan_state.scan_explicit_params_mutex); + return err; + } + + /* store the parameters that were used to start the scanner */ + memcpy(&scan_state.explicit_scan_param, param, + sizeof(scan_state.explicit_scan_param)); + + scan_dev_found_cb = cb; + err = bt_le_scan_user_add(BT_LE_SCAN_USER_EXPLICIT_SCAN); + k_mutex_unlock(&scan_state.scan_explicit_params_mutex); + + return err; } int bt_le_scan_stop(void) { - /* Return if active scanning is already disabled */ - if (!atomic_test_and_clear_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { - return -EALREADY; - } - - bt_scan_reset(); + bt_scan_softreset(); + scan_dev_found_cb = NULL; if (IS_ENABLED(CONFIG_BT_EXT_ADV) && atomic_test_and_clear_bit(bt_dev.flags, BT_DEV_SCAN_LIMITED)) { @@ -1672,7 +1781,7 @@ int bt_le_scan_stop(void) #endif } - return bt_le_scan_update(false); + return bt_le_scan_user_remove(BT_LE_SCAN_USER_EXPLICIT_SCAN); } int bt_le_scan_cb_register(struct bt_le_scan_cb *cb) @@ -1844,13 +1953,16 @@ int bt_le_per_adv_sync_create(const struct bt_le_per_adv_sync_param *param, * established. We don't need to use any callbacks since we rely on * the advertiser address in the sync params. */ - if (!atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) { - err = bt_le_scan_update(true); + err = bt_le_scan_user_add(BT_LE_SCAN_USER_PER_SYNC); + if (err) { + int per_sync_remove_err = bt_le_scan_user_remove(BT_LE_SCAN_USER_PER_SYNC); - if (err) { - bt_le_per_adv_sync_delete(per_adv_sync); - return err; + if (per_sync_remove_err) { + LOG_WRN("Error while updating the scanner (%d)", per_sync_remove_err); } + + bt_le_per_adv_sync_delete(per_adv_sync); + return err; } *out_sync = per_adv_sync; @@ -1870,6 +1982,12 @@ static int bt_le_per_adv_sync_create_cancel( return -EINVAL; } + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_PER_SYNC); + + if (err) { + return err; + } + buf = bt_hci_cmd_create(BT_HCI_OP_LE_PER_ADV_CREATE_SYNC_CANCEL, 0); if (!buf) { return -ENOBUFS; @@ -2285,3 +2403,8 @@ int bt_le_per_adv_list_clear(void) return 0; } #endif /* defined(CONFIG_BT_PER_ADV_SYNC) */ + +bool bt_le_explicit_scanner_running(void) +{ + return atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_EXPLICIT_SCAN); +} diff --git a/subsys/bluetooth/host/scan.h b/subsys/bluetooth/host/scan.h index d3693596bd8..b30adfca3ba 100644 --- a/subsys/bluetooth/host/scan.h +++ b/subsys/bluetooth/host/scan.h @@ -5,10 +5,120 @@ * SPDX-License-Identifier: Apache-2.0 */ +#ifndef SUBSYS_BLUETOOTH_HOST_SCAN_H_ +#define SUBSYS_BLUETOOTH_HOST_SCAN_H_ + +#include + +#include +#include + +/** + * Reasons why a scanner can be running. + * Used as input to @ref bt_le_scan_user_add + * and @ref bt_le_scan_user_remove. + */ +enum bt_le_scan_user { + /** The application explicitly instructed the stack to scan for advertisers + * using the API @ref bt_le_scan_start. + * Users of the scanner module may not use this flag as input to @ref bt_le_scan_user_add + * and @ref bt_le_scan_user_remove. Use ®ref bt_le_scan_start and @ref bt_le_scan_stop + * instead. + */ + BT_LE_SCAN_USER_EXPLICIT_SCAN, + + /** + * Periodic sync syncing to a periodic advertiser. + */ + BT_LE_SCAN_USER_PER_SYNC, + + /** + * Scanning to find devices to connect to. + */ + BT_LE_SCAN_USER_CONN, + + /** + * Special state for a NOP for @ref bt_le_scan_user_add and @ref bt_le_scan_user_remove to + * not add/remove any user. + */ + BT_LE_SCAN_USER_NONE, + BT_LE_SCAN_USER_NUM_FLAGS, +}; + void bt_scan_reset(void); bool bt_id_scan_random_addr_check(void); +bool bt_le_scan_active_scanner_running(void); int bt_le_scan_set_enable(uint8_t enable); void bt_periodic_sync_disable(void); + +/** + * Start / update the scanner. + * + * This API updates the users of the scanner. + * Multiple users can be enabled at the same time. + * Depending on all the users, scan parameters are selected + * and the scanner is started or updated, if needed. + * This API may update the scan parameters, for example if the scanner is already running + * when another user that demands higher duty-cycle is being added. + * It is not allowed to add a user that was already added. + * + * Every SW module that informs the scanner that it should run, needs to eventually remove + * the flag again using @ref bt_le_scan_user_remove once it does not require + * the scanner to run, anymore. + * + * If flag is set to @ref BT_LE_SCAN_USER_NONE, no user is being added. Instead, the + * existing users are checked and the scanner is started, stopped or updated. + * For platforms where scanning and initiating at the same time is not supported, + * this allows the background scanner to be started or stopped once the device starts to + * initiate a connection. + * + * @param flag user requesting the scanner + * + * @retval 0 in case of success + * @retval -EALREADY if the user is already enabled + * @retval -EPERM if the explicit scanner is being enabled while the initiator is running + * and the device does not support this configuration. + * @retval -ENOBUFS if no hci command buffer could be allocated + * @retval -EBUSY if the scanner is updated in a different thread. The user was added but + * the scanner was not started/stopped/updated. + * @returns negative error codes for errors in @ref bt_hci_cmd_send_sync + */ +int bt_le_scan_user_add(enum bt_le_scan_user flag); + +/** + * Stop / update the scanner. + * + * This API updates the users of the scanner. + * Depending on all enabled users, scan parameters are selected + * and the scanner is stopped or updated, if needed. + * This API may update the scan parameters, for example if the scanner is already running + * when a user that demands higher duty-cycle is being removed. + * Removing a user that was not added does not result in an error. + * + * This API allows removing the user why the scanner is running. + * If all users for the scanner to run are removed, this API will stop the scanner. + * + * If flag is set to @ref BT_LE_SCAN_USER_NONE, no user is being added. Instead, the + * existing users are checked and the scanner is started, stopped or updated. + * For platforms where scanning and initiating at the same time is not supported, + * this allows the background scanner to be started or stopped once the device starts to + * initiate a connection. + * + * @param flag user releasing the scanner + * + * @retval 0 in case of success + * @retval -ENOBUFS if no hci command buffer could be allocated + * @retval -EBUSY if the scanner is updated in a different thread. The user was removed but + * the scanner was not started/stopped/updated. + * @returns negative error codes for errors in @ref bt_hci_cmd_send_sync + */ +int bt_le_scan_user_remove(enum bt_le_scan_user flag); + +/** + * Check if the explicit scanner was enabled. + */ +bool bt_le_explicit_scanner_running(void); +#endif /* defined SUBSYS_BLUETOOTH_HOST_SCAN_H_ */ diff --git a/tests/bluetooth/host/id/mocks/scan.c b/tests/bluetooth/host/id/mocks/scan.c index bc91e861407..d9921e6f0b1 100644 --- a/tests/bluetooth/host/id/mocks/scan.c +++ b/tests/bluetooth/host/id/mocks/scan.c @@ -9,3 +9,4 @@ #include DEFINE_FAKE_VALUE_FUNC(int, bt_le_scan_set_enable, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, bt_le_scan_active_scanner_running); diff --git a/tests/bluetooth/host/id/mocks/scan.h b/tests/bluetooth/host/id/mocks/scan.h index 04fae9097a8..a54d43b643c 100644 --- a/tests/bluetooth/host/id/mocks/scan.h +++ b/tests/bluetooth/host/id/mocks/scan.h @@ -11,3 +11,4 @@ #define SCAN_FFF_FAKES_LIST(FAKE) FAKE(bt_le_scan_set_enable) DECLARE_FAKE_VALUE_FUNC(int, bt_le_scan_set_enable, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, bt_le_scan_active_scanner_running); diff --git a/tests/bsim/bluetooth/host/compile.sh b/tests/bsim/bluetooth/host/compile.sh index c78c7a735a3..b735ab26c1b 100755 --- a/tests/bsim/bluetooth/host/compile.sh +++ b/tests/bsim/bluetooth/host/compile.sh @@ -39,5 +39,6 @@ run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/privacy/device/compil run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/privacy/legacy/compile.sh run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/id/settings/compile.sh +run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/scan/start_stop/compile.sh wait_for_background_jobs diff --git a/tests/bsim/bluetooth/host/scan/start_stop/CMakeLists.txt b/tests/bsim/bluetooth/host/scan/start_stop/CMakeLists.txt new file mode 100644 index 00000000000..c15553e7080 --- /dev/null +++ b/tests/bsim/bluetooth/host/scan/start_stop/CMakeLists.txt @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) +project(bsim_test_scan_start_stop) + +target_sources(app PRIVATE + src/main.c +) + +# This contains babblesim-specific helpers, e.g. device synchronization. +add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit) +target_link_libraries(app PRIVATE babblekit) + +zephyr_include_directories( + $ENV{BSIM_COMPONENTS_PATH}/libUtilv1/src/ + $ENV{BSIM_COMPONENTS_PATH}/libPhyComv1/src/ +) diff --git a/tests/bsim/bluetooth/host/scan/start_stop/compile.sh b/tests/bsim/bluetooth/host/scan/start_stop/compile.sh new file mode 100755 index 00000000000..718910a63c4 --- /dev/null +++ b/tests/bsim/bluetooth/host/scan/start_stop/compile.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Copyright 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +set -eu +: "${ZEPHYR_BASE:?ZEPHYR_BASE must be defined}" + +INCR_BUILD=1 + +source ${ZEPHYR_BASE}/tests/bsim/compile.source + +app="$(guess_test_relpath)" compile + +wait_for_background_jobs diff --git a/tests/bsim/bluetooth/host/scan/start_stop/prj.conf b/tests/bsim/bluetooth/host/scan/start_stop/prj.conf new file mode 100644 index 00000000000..48b458fa25a --- /dev/null +++ b/tests/bsim/bluetooth/host/scan/start_stop/prj.conf @@ -0,0 +1,13 @@ +CONFIG_BT=y +CONFIG_BT_BROADCASTER=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_OBSERVER=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_PER_ADV_SYNC=y +CONFIG_BT_PER_ADV=y +CONFIG_BT_DEVICE_NAME="Scanner Test" +CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL=y +CONFIG_BT_PRIVACY=n + +CONFIG_LOG=y +CONFIG_ASSERT=y diff --git a/tests/bsim/bluetooth/host/scan/start_stop/src/main.c b/tests/bsim/bluetooth/host/scan/start_stop/src/main.c new file mode 100644 index 00000000000..b266d887507 --- /dev/null +++ b/tests/bsim/bluetooth/host/scan/start_stop/src/main.c @@ -0,0 +1,268 @@ +/* Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_bsim_scan_start_stop, LOG_LEVEL_DBG); + +#define WAIT_TIME_S 60 +#define WAIT_TIME (WAIT_TIME_S * 1e6) + +static atomic_t flag_adv_report_received; +static atomic_t flag_periodic_sync_established; +static bt_addr_le_t adv_addr; + +static void test_tick(bs_time_t HW_device_time) +{ + if (bst_result != Passed) { + TEST_FAIL("Test failed (not passed after %d seconds)\n", WAIT_TIME_S); + } +} + +static void test_init(void) +{ + bst_ticker_set_next_tick_absolute(WAIT_TIME); +} + +static void bt_sync_established_cb(struct bt_le_per_adv_sync *sync, + struct bt_le_per_adv_sync_synced_info *info) +{ + LOG_DBG("Periodic sync established"); + atomic_set(&flag_periodic_sync_established, true); +} + +static struct bt_le_per_adv_sync_cb sync_callbacks = { + .synced = bt_sync_established_cb, +}; + +static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, + struct net_buf_simple *ad) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + + memcpy(&adv_addr, addr, sizeof(adv_addr)); + + bt_addr_le_to_str(&adv_addr, addr_str, sizeof(addr_str)); + LOG_DBG("Device found: %s (RSSI %d), type %u, AD data len %u", + addr_str, rssi, type, ad->len); + atomic_set(&flag_adv_report_received, true); +} + +void run_dut(void) +{ + /* Test purpose: + * + * Verifies that the scanner can establish a sync to a device when + * it is explicitly enabled and disabled. Disabling the scanner + * explicitly should not stop the implicitly started scanner. + * Verify that the explicit scanner can be started when the + * implicit scanner is already running. + * + * Two devices: + * - `dut`: tries to establish the sync + * - `peer`: runs an advertiser / periodic advertiser + * + * Procedure: + * - [dut] start establishing a sync (no peer) + * - [peer] starts advertising + * - [dut] starts explicit scanning + * - [dut] wait for the peer to be found + * - [dut] stops explicit scanning + * - [dut] stop the periodic sync + * - [dut] start establishing a sync to the peer + * - [dut] start and stop explicit scanning + * - [peer] start periodic advertiser + * - [dut] wait until a sync is established + * + * [verdict] + * - dut is able to sync to the peer. + */ + + LOG_DBG("start"); + bk_sync_init(); + int err; + + LOG_DBG("Starting DUT"); + + err = bt_enable(NULL); + TEST_ASSERT(!err, "Bluetooth init failed (err %d)\n", err); + + LOG_DBG("Bluetooth initialised"); + + /* Try to establish a sync to a periodic advertiser. + * This will start the scanner. + */ + memset(&adv_addr, 0x00, sizeof(adv_addr)); + struct bt_le_per_adv_sync_param per_sync_param = { + .addr = adv_addr, + .options = 0x0, + .sid = 0x0, + .skip = 0x0, + .timeout = BT_GAP_PER_ADV_MAX_TIMEOUT, + }; + struct bt_le_per_adv_sync *p_per_sync; + + bt_le_per_adv_sync_cb_register(&sync_callbacks); + + err = bt_le_per_adv_sync_create(&per_sync_param, &p_per_sync); + TEST_ASSERT(!err, "Periodic sync setup failed (err %d)\n", err); + LOG_DBG("Periodic sync started"); + + /* Start scanner. Check that we can start the scanner while it is already + * running due to the periodic sync. + */ + struct bt_le_scan_param scan_params = { + .type = BT_LE_SCAN_TYPE_ACTIVE, + .options = 0x0, + .interval = 123, + .window = 12, + .interval_coded = 222, + .window_coded = 32, + }; + + err = bt_le_scan_start(&scan_params, device_found); + TEST_ASSERT(!err, "Scanner setup failed (err %d)\n", err); + LOG_DBG("Explicit scanner started"); + + LOG_DBG("Wait for an advertising report"); + WAIT_FOR_FLAG(flag_adv_report_received); + + /* Stop the scanner. That should not affect the periodic advertising sync. */ + err = bt_le_scan_stop(); + TEST_ASSERT(!err, "Scanner stop failed (err %d)\n", err); + LOG_DBG("Explicit scanner stopped"); + + /* We should be able to stop the periodic advertising sync. */ + err = bt_le_per_adv_sync_delete(p_per_sync); + TEST_ASSERT(!err, "Periodic sync stop failed (err %d)\n", err); + LOG_DBG("Periodic sync stopped"); + + /* Start the periodic advertising sync. This time, provide the address of the advertiser + * which it should connect to. + */ + per_sync_param = (struct bt_le_per_adv_sync_param) { + .addr = adv_addr, + .options = 0x0, + .sid = 0x0, + .skip = 0x0, + .timeout = BT_GAP_PER_ADV_MAX_TIMEOUT + }; + err = bt_le_per_adv_sync_create(&per_sync_param, &p_per_sync); + TEST_ASSERT(!err, "Periodic sync setup failed (err %d)\n", err); + LOG_DBG("Periodic sync started"); + + /* Start the explicit scanner */ + err = bt_le_scan_start(&scan_params, device_found); + TEST_ASSERT(!err, "Scanner setup failed (err %d)\n", err); + LOG_DBG("Explicit scanner started"); + + /* Stop the explicit scanner. This should not stop scanner, since we still try to establish + * a sync. + */ + err = bt_le_scan_stop(); + TEST_ASSERT(!err, "Scanner stop failed (err %d)\n", err); + LOG_DBG("Explicit scanner stopped"); + + /* Signal to the tester to start the periodic adv. */ + bk_sync_send(); + + /* Validate that we can still establish a sync */ + LOG_DBG("Wait for sync to periodic adv"); + WAIT_FOR_FLAG(flag_periodic_sync_established); + + /* Signal to the tester to end the test. */ + bk_sync_send(); + + TEST_PASS("Test passed (DUT)\n"); +} + +void run_tester(void) +{ + LOG_DBG("start"); + bk_sync_init(); + + int err; + + LOG_DBG("Starting DUT"); + + err = bt_enable(NULL); + TEST_ASSERT(!err, "Bluetooth init failed (err %d)\n", err); + + LOG_DBG("Bluetooth initialised"); + + struct bt_le_ext_adv *per_adv; + + struct bt_le_adv_param adv_param = BT_LE_ADV_PARAM_INIT(BT_LE_ADV_OPT_EXT_ADV, + BT_GAP_ADV_FAST_INT_MIN_2, + BT_GAP_ADV_FAST_INT_MAX_2, + NULL); + + err = bt_le_ext_adv_create(&adv_param, NULL, &per_adv); + TEST_ASSERT(!err, "Failed to create advertising set: %d", err); + LOG_DBG("Created extended advertising set."); + + err = bt_le_ext_adv_start(per_adv, BT_LE_EXT_ADV_START_DEFAULT); + TEST_ASSERT(!err, "Failed to start extended advertising: %d", err); + LOG_DBG("Started extended advertising."); + + /* Wait for the DUT before starting the periodic advertising */ + bk_sync_wait(); + err = bt_le_per_adv_set_param(per_adv, BT_LE_PER_ADV_DEFAULT); + TEST_ASSERT(!err, "Failed to set periodic advertising parameters: %d", err); + LOG_DBG("Periodic advertising parameters set."); + + err = bt_le_per_adv_start(per_adv); + TEST_ASSERT(!err, "Failed to start periodic advertising: %d", err); + LOG_DBG("Periodic advertising started."); + + /* Wait for the signal from the DUT before finishing the test */ + bk_sync_wait(); + + bt_le_per_adv_stop(per_adv); + + TEST_PASS("Test passed (Tester)\n"); +} + +static const struct bst_test_instance test_def[] = { + { + .test_id = "scanner", + .test_descr = "SCANNER", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = run_dut, + }, + { + .test_id = "periodic_adv", + .test_descr = "PER_ADV", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = run_tester, + }, + BSTEST_END_MARKER +}; + +struct bst_test_list *test_scan_start_stop_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_def); +} + +bst_test_install_t test_installers[] = {test_scan_start_stop_install, NULL}; + +int main(void) +{ + bst_main(); + return 0; +} diff --git a/tests/bsim/bluetooth/host/scan/start_stop/test_scripts/start_stop.sh b/tests/bsim/bluetooth/host/scan/start_stop/test_scripts/start_stop.sh new file mode 100755 index 00000000000..2e172fdde7c --- /dev/null +++ b/tests/bsim/bluetooth/host/scan/start_stop/test_scripts/start_stop.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Copyright 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +test_exe="bs_${BOARD_TS}_tests_bsim_bluetooth_host_scan_start_stop_prj_conf" +simulation_id="start_stop" +verbosity_level=2 + +cd ${BSIM_OUT_PATH}/bin + +Execute "./${test_exe}" \ + -v=${verbosity_level} -s="${simulation_id}" -d=0 -testid=scanner + +Execute "./${test_exe}" \ + -v=${verbosity_level} -s="${simulation_id}" -d=1 -testid=periodic_adv + +Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s="${simulation_id}" \ + -D=2 -sim_length=5e6 + +wait_for_background_jobs