diff --git a/nimble/host/include/host/ble_gap.h b/nimble/host/include/host/ble_gap.h index 2a322c8ea..cef0a557d 100644 --- a/nimble/host/include/host/ble_gap.h +++ b/nimble/host/include/host/ble_gap.h @@ -1141,6 +1141,53 @@ struct ble_gap_event { }; }; + +#if MYNEWT_VAL(OPTIMIZE_MULTI_CONN) +/** @brief multiple Connection parameters */ +struct ble_gap_multi_conn_params { + /** The type of address the stack should use for itself. */ + uint8_t own_addr_type; + +#if MYNEWT_VAL(BLE_EXT_ADV) + /** Define on which PHYs connection attempt should be done */ + uint8_t phy_mask; +#endif // MYNEWT_VAL(BLE_EXT_ADV) + + /** The address of the peer to connect to. */ + const ble_addr_t *peer_addr; + + /** The duration of the discovery procedure. */ + int32_t duration_ms; + + /** + * Additional arguments specifying the particulars of the connect procedure. When extended + * adv is disabled or BLE_GAP_LE_PHY_1M_MASK is set in phy_mask this parameter can't be + * specified to null. + */ + const struct ble_gap_conn_params *phy_1m_conn_params; + +#if MYNEWT_VAL(BLE_EXT_ADV) + /** + * Additional arguments specifying the particulars of the connect procedure. When + * BLE_GAP_LE_PHY_2M_MASK is set in phy_mask this parameter can't be specified to null. + */ + const struct ble_gap_conn_params *phy_2m_conn_params; + + /** + * Additional arguments specifying the particulars of the connect procedure. When + * BLE_GAP_LE_PHY_CODED_MASK is set in phy_mask this parameter can't be specified to null. + */ + const struct ble_gap_conn_params *phy_coded_conn_params; +#endif // MYNEWT_VAL(BLE_EXT_ADV) + + /** + * The minimum length occupied by this connection in scheduler. 0 means disable the + * optimization for this connection. + */ + uint32_t scheduling_len_us; +}; +#endif // MYNEWT_VAL(OPTIMIZE_MULTI_CONN) + typedef int ble_gap_event_fn(struct ble_gap_event *event, void *arg); typedef int ble_gap_conn_foreach_handle_fn(uint16_t conn_handle, void *arg); @@ -1937,6 +1984,44 @@ int ble_gap_ext_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, const struct ble_gap_conn_params *phy_coded_conn_params, ble_gap_event_fn *cb, void *cb_arg); +#if MYNEWT_VAL(OPTIMIZE_MULTI_CONN) +/** + * @brief Enable the optimization of multiple connections. + * + * @param enable Enable or disable the optimization. + * @param common_factor The greatest common factor of all intervals in 0.625ms units. + * @return 0 on success; + * + */ +int ble_gap_common_factor_set(bool enable, uint32_t common_factor); + +/** + * Initiates an extended connect procedure with optimization. + * + * @param multi_conn_params Additional arguments specifying the particulars + * of the connect procedure. + * @param cb The callback to associate with this connect + * procedure. When the connect procedure + * completes, the result is reported through + * this callback. If the connect procedure + * succeeds, the connection inherits this + * callback as its event-reporting mechanism. + * @param cb_arg The optional argument to pass to the callback + * function. + * + * @return 0 on success; + * BLE_HS_EALREADY if a connection attempt is + * already in progress; + * BLE_HS_EBUSY if initiating a connection is not + * possible because scanning is in progress; + * BLE_HS_EDONE if the specified peer is already + * connected; + * Other nonzero on error. + */ +int ble_gap_multi_connect(struct ble_gap_multi_conn_params *multi_conn_params, + ble_gap_event_fn *cb, void *cb_arg); +#endif + /** * Aborts a connect procedure in progress. * diff --git a/nimble/host/src/ble_gap.c b/nimble/host/src/ble_gap.c index 0b177650a..156c26c56 100644 --- a/nimble/host/src/ble_gap.c +++ b/nimble/host/src/ble_gap.c @@ -235,6 +235,17 @@ static os_membuf_t ble_gap_update_entry_mem[ static struct os_mempool ble_gap_update_entry_pool; static struct ble_gap_update_entry_list ble_gap_update_entries; +#if MYNEWT_VAL(OPTIMIZE_MULTI_CONN) +struct ble_gap_multi_conn_state +{ + bool enabled; + bool scheduling_len_set; + uint32_t common_factor; +}; + +static struct ble_gap_multi_conn_state ble_gap_multi_conn; +#endif + static void ble_gap_update_entry_free(struct ble_gap_update_entry *entry); #if NIMBLE_BLE_CONNECT @@ -5337,6 +5348,15 @@ ble_gap_ext_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, STATS_INC(ble_gap_stats, initiate); +#if MYNEWT_VAL(OPTIMIZE_MULTI_CONN) + /* If the optimization is enabled, we disallow to invoke this API directly. + * See @ble_gap_multi_connect() + */ + if (ble_gap_multi_conn.enabled && !ble_gap_multi_conn.scheduling_len_set) { + return BLE_HS_EINVAL; + } +#endif // MYNEWT_VAL(OPTIMIZE_MULTI_CONN) + ble_hs_lock(); if (ble_gap_conn_active()) { @@ -5466,6 +5486,15 @@ ble_gap_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, STATS_INC(ble_gap_stats, initiate); +#if MYNEWT_VAL(OPTIMIZE_MULTI_CONN) + /* If the optimization is enabled, we disallow to invoke this API directly. + * See @ble_gap_multi_connect() + */ + if (ble_gap_multi_conn.enabled && !ble_gap_multi_conn.scheduling_len_set) { + return BLE_HS_EINVAL; + } +#endif // MYNEWT_VAL(OPTIMIZE_MULTI_CONN) + ble_hs_lock(); if (ble_gap_conn_active()) { @@ -5593,9 +5622,155 @@ done: #else return BLE_HS_ENOTSUP; #endif - } +#if MYNEWT_VAL(OPTIMIZE_MULTI_CONN) +static bool +ble_gap_interval_is_integer_multiple(uint32_t min_itvl, uint32_t max_itvl) +{ + if (max_itvl < ble_gap_multi_conn.common_factor) { + return false; + } + + max_itvl = max_itvl - (max_itvl % ble_gap_multi_conn.common_factor); + if (max_itvl >= min_itvl) { + return true; + } + return false; +} + +int +ble_gap_common_factor_set(bool enable, uint32_t common_factor) +{ + int rc; + uint8_t vs_cmd[5]; + + if (!ble_hs_is_enabled()) { + return BLE_HS_EDISABLED; + } + + if (enable) { + vs_cmd[0] = common_factor & 0xfful; + vs_cmd[1] = (common_factor >> 8) & 0xfful; + vs_cmd[2] = (common_factor >> 16) & 0xfful; + vs_cmd[3] = (common_factor >> 24) & 0xfful; + vs_cmd[4] = 1; + } else { + memset(vs_cmd, 0, sizeof(vs_cmd)); + } + + rc = ble_hs_hci_send_vs_cmd(0x10f, (const void *)vs_cmd, sizeof(vs_cmd), NULL, 0); + if (rc == 0) { + if (enable) { + ble_gap_multi_conn.enabled = true; + ble_gap_multi_conn.common_factor = common_factor; + } else { + ble_gap_multi_conn.enabled = false; + ble_gap_multi_conn.common_factor = 0; + } + } + + return rc; +} + +#if MYNEWT_VAL(BLE_ROLE_CENTRAL) +int +ble_gap_multi_connect(struct ble_gap_multi_conn_params *multi_conn_params, + ble_gap_event_fn *cb, void *cb_arg) +{ + int rc; + uint8_t vs_cmd[5]; + uint32_t scheduling_len_us; + const struct ble_gap_conn_params *conn_params; +#if MYNEWT_VAL(BLE_EXT_ADV) + uint8_t phy_mask; +#endif // MYNEWT_VAL(BLE_EXT_ADV) + + if (!ble_hs_is_enabled()) { + return BLE_HS_EDISABLED; + } + + if (!ble_gap_multi_conn.enabled || (multi_conn_params == NULL)) { + return BLE_HS_EINVAL; + } + +#if MYNEWT_VAL(BLE_EXT_ADV) + phy_mask = multi_conn_params->phy_mask; + if (phy_mask == 0) { + return BLE_HS_EINVAL; + } +#endif // MYNEWT_VAL(BLE_EXT_ADV) + + scheduling_len_us = multi_conn_params->scheduling_len_us; + /* `scheduling_len_us == 0` is allowed. It indicates that the optimization for this connection + * is disabled. The connection interval must be an integer multiple of `common factor`. Note + * that the unit of the connection interval is 1.25ms, while the common factor's unit is 0.625ms. + */ + if (scheduling_len_us != 0) { +#if MYNEWT_VAL(BLE_EXT_ADV) + if (phy_mask & BLE_GAP_LE_PHY_1M_MASK) { + conn_params = multi_conn_params->phy_1m_conn_params; + if ((conn_params == NULL) || + !ble_gap_interval_is_integer_multiple(conn_params->itvl_min << 1, + conn_params->itvl_max << 1)) { + return BLE_HS_EINVAL; + } + } + if (phy_mask & BLE_GAP_LE_PHY_2M_MASK) { + conn_params = multi_conn_params->phy_2m_conn_params; + if ((conn_params == NULL) || + !ble_gap_interval_is_integer_multiple(conn_params->itvl_min << 1, + conn_params->itvl_max << 1)) { + return BLE_HS_EINVAL; + } + } + if (phy_mask & BLE_GAP_LE_PHY_CODED_MASK) { + conn_params = multi_conn_params->phy_coded_conn_params; + if ((conn_params == NULL) || + !ble_gap_interval_is_integer_multiple(conn_params->itvl_min << 1, + conn_params->itvl_max << 1)) { + return BLE_HS_EINVAL; + } + } +#else + conn_params = multi_conn_params->phy_1m_conn_params; + if ((conn_params == NULL) || + !ble_gap_interval_is_integer_multiple(conn_params->itvl_min << 1, + conn_params->itvl_max << 1)) { + return BLE_HS_EINVAL; + } +#endif // MYNEWT_VAL(BLE_EXT_ADV) + } + + vs_cmd[0] = 0; + vs_cmd[1] = scheduling_len_us & 0xfful; + vs_cmd[2] = (scheduling_len_us >> 8) & 0xfful; + vs_cmd[3] = (scheduling_len_us >> 16) & 0xfful; + vs_cmd[4] = (scheduling_len_us >> 24) & 0xfful; + rc = ble_hs_hci_send_vs_cmd(0x110, (const void *)vs_cmd, sizeof(vs_cmd), NULL, 0); + if (rc != 0) { + return rc; + } + + ble_gap_multi_conn.scheduling_len_set = true; +#if MYNEWT_VAL(BLE_EXT_ADV) + rc = ble_gap_ext_connect(multi_conn_params->own_addr_type, multi_conn_params->peer_addr, + multi_conn_params->duration_ms, multi_conn_params->phy_mask, + multi_conn_params->phy_1m_conn_params, + multi_conn_params->phy_2m_conn_params, + multi_conn_params->phy_coded_conn_params, cb, cb_arg); +#else + rc = ble_gap_ext_connect(multi_conn_params->own_addr_type, multi_conn_params->peer_addr, + multi_conn_params->duration_ms, multi_conn_params->phy_1m_conn_params, + cb, cb_arg); +#endif // MYNEWT_VAL(BLE_EXT_ADV) + ble_gap_multi_conn.scheduling_len_set = false; + + return rc; +} +#endif // MYNEWT_VAL(BLE_ROLE_CENTRAL) +#endif + int ble_gap_conn_active(void) { @@ -6777,6 +6952,10 @@ ble_gap_init(void) memset(&ble_gap_master, 0, sizeof(ble_gap_master)); memset(ble_gap_slave, 0, sizeof(ble_gap_slave)); +#if MYNEWT_VAL(OPTIMIZE_MULTI_CONN) + memset(&ble_gap_multi_conn, 0, sizeof(ble_gap_multi_conn)); +#endif + #if MYNEWT_VAL(BLE_PERIODIC_ADV) memset(&ble_gap_sync, 0, sizeof(ble_gap_sync)); #endif