diff --git a/nimble/host/include/host/ble_hs.h b/nimble/host/include/host/ble_hs.h index 82d7ff5a1..ba874b1c9 100644 --- a/nimble/host/include/host/ble_hs.h +++ b/nimble/host/include/host/ble_hs.h @@ -38,6 +38,7 @@ #include "host/ble_hs_log.h" #include "host/ble_hs_test.h" #include "host/ble_hs_mbuf.h" +#include "host/ble_hs_stop.h" #include "host/ble_ibeacon.h" #include "host/ble_l2cap.h" #include "host/ble_sm.h" @@ -93,6 +94,7 @@ extern "C" { #define BLE_HS_ESTORE_CAP 27 #define BLE_HS_ESTORE_FAIL 28 #define BLE_HS_EPREEMPTED 29 +#define BLE_HS_EDISABLED 30 /** Error base for ATT errors */ #define BLE_HS_ERR_ATT_BASE 0x100 @@ -287,12 +289,21 @@ extern struct ble_hs_cfg ble_hs_cfg; * @} */ +/** + * @brief Indicates whether the host is enabled. The host is enabled if it is + * starting or fully started. It is disabled if it is stopping or stopped. + * + * @return 1 if the host is enabled; + * 0 if the host is disabled. + */ +int ble_hs_enabled(void); + /** * Indicates whether the host has synchronized with the controller. * Synchronization must occur before any host procedures can be performed. * * @return 1 if the host and controller are in sync; - * 0 if the host and controller our out of sync. + * 0 if the host and controller are out of sync. */ int ble_hs_synced(void); diff --git a/nimble/host/include/host/ble_hs_stop.h b/nimble/host/include/host/ble_hs_stop.h new file mode 100644 index 000000000..d16c9c27b --- /dev/null +++ b/nimble/host/include/host/ble_hs_stop.h @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLE_HS_STOP_ +#define H_BLE_HS_STOP_ + +/** @typedef ble_hs_stop_fn + * @brief Callback function; reports the result of a host stop procedure. + * + * @param status The result of the host stop procedure. One of + * the HAL_RESET_[...] codes or an + * implementation-defined value. + * @param arg Optional argument specified when the stop + * procedure was initiated. + * + */ +typedef void ble_hs_stop_fn(int status, void *arg); + +/** + * @brief Used to report the result of a stop procedure. + * + * This should be used as an opaque structure and not modified manually. + */ +struct ble_hs_stop_listener { + ble_hs_stop_fn *fn; + void *arg; + SLIST_ENTRY(ble_hs_stop_listener) link; +}; + +/** + * @brief Stops the BLE host. + * + * Aborts all active GAP procedures and terminates all open connections. + * Connection termination is performed asynchronously, so this function's + * result is reported via the provided listener. + * + * @param listener A listener to populate. This object's initial + * value doesn't matter, but its lifetime must + * extend until the stop procedure completes. + * @param fn The callback to execute when the stop procedure + * completes. + * @param arg Optional argument to pass to the callback. + * + * @return 0: Stop procedure successfully initiated. + * BLE_HS_EBUSY: Stop procedure already in + * progress; the provided callback gets called + * when the procedure completes. + * BLE_HS_EALREADY: Host already stopped; the + * provided callback does *not* get called. + */ +int ble_hs_stop(struct ble_hs_stop_listener *listener, + ble_hs_stop_fn *fn, void *arg); + +#endif diff --git a/nimble/host/include/host/ble_hs_test.h b/nimble/host/include/host/ble_hs_test.h index 8fc580df8..feb510476 100644 --- a/nimble/host/include/host/ble_hs_test.h +++ b/nimble/host/include/host/ble_hs_test.h @@ -50,6 +50,7 @@ int ble_hs_pvcy_test_all(void); int ble_sm_lgcy_test_suite(void); int ble_sm_sc_test_suite(void); int ble_sm_test_all(void); +int ble_stop_test_all(void); int ble_store_test_all(void); int ble_uuid_test_all(void); diff --git a/nimble/host/src/ble_gap.c b/nimble/host/src/ble_gap.c index b931b6686..e131a7d77 100644 --- a/nimble/host/src/ble_gap.c +++ b/nimble/host/src/ble_gap.c @@ -2093,6 +2093,11 @@ ble_gap_adv_start(uint8_t own_addr_type, const ble_addr_t *direct_addr, } } + if (!ble_hs_enabled()) { + rc = BLE_HS_EDISABLED; + goto done; + } + if (ble_gap_is_preempted()) { rc = BLE_HS_EPREEMPTED; goto done; @@ -3099,6 +3104,10 @@ ble_gap_disc_ext_validate(uint8_t own_addr_type) return BLE_HS_EALREADY; } + if (!ble_hs_enabled()) { + return BLE_HS_EDISABLED; + } + if (ble_gap_is_preempted()) { return BLE_HS_EPREEMPTED; } @@ -3579,6 +3588,10 @@ ble_gap_ext_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, goto done; } + if (!ble_hs_enabled()) { + return BLE_HS_EDISABLED; + } + if (ble_gap_is_preempted()) { rc = BLE_HS_EPREEMPTED; goto done; @@ -3702,6 +3715,11 @@ ble_gap_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, goto done; } + if (!ble_hs_enabled()) { + rc = BLE_HS_EDISABLED; + goto done; + } + if (ble_gap_is_preempted()) { rc = BLE_HS_EPREEMPTED; goto done; @@ -3796,6 +3814,7 @@ ble_gap_conn_active(void) /***************************************************************************** * $terminate connection procedure * *****************************************************************************/ + int ble_gap_terminate(uint16_t conn_handle, uint8_t hci_reason) { @@ -4613,28 +4632,14 @@ ble_gap_mtu_event(uint16_t conn_handle, uint16_t cid, uint16_t mtu) * $preempt * *****************************************************************************/ -/** - * Aborts all active GAP procedures and prevents new ones from being started. - * This function is used to ensure an idle GAP so that the controller's - * resolving list can be modified. When done accessing the resolving list, the - * caller must call `ble_gap_preempt_done()` to permit new GAP procedures. - * - * On preemption, all aborted GAP procedures are reported with a status or - * reason code of BLE_HS_EPREEMPTED. An attempt to initiate a new GAP - * procedure during preemption fails with a return code of BLE_HS_EPREEMPTED. - */ void -ble_gap_preempt(void) +ble_gap_preempt_no_lock(void) { int rc; int i; (void)rc; - (void) i; - - ble_hs_lock(); - - BLE_HS_DBG_ASSERT(!ble_gap_is_preempted()); + (void)i; #if NIMBLE_BLE_ADVERTISE #if MYNEWT_VAL(BLE_EXT_ADV) @@ -4665,13 +4670,47 @@ ble_gap_preempt(void) ble_gap_master.preempted_op = BLE_GAP_OP_M_DISC; } #endif +} + +/** + * Aborts all active GAP procedures and prevents new ones from being started. + * This function is used to ensure an idle GAP so that the controller's + * resolving list can be modified. When done accessing the resolving list, the + * caller must call `ble_gap_preempt_done()` to permit new GAP procedures. + * + * On preemption, all aborted GAP procedures are reported with a status or + * reason code of BLE_HS_EPREEMPTED. An attempt to initiate a new GAP + * procedure during preemption fails with a return code of BLE_HS_EPREEMPTED. + */ +void +ble_gap_preempt(void) +{ + ble_hs_lock(); + + BLE_HS_DBG_ASSERT(!ble_gap_is_preempted()); + ble_gap_preempt_no_lock(); + + ble_hs_unlock(); +} + +/** + * Preempts the GAP if it is not already preempted. + */ +void +ble_gap_ensure_preempted(void) +{ + ble_hs_lock(); + + if (!ble_gap_is_preempted()) { + ble_gap_preempt_no_lock(); + } ble_hs_unlock(); } /** * Takes GAP out of the preempted state, allowing new GAP procedures to be - * initiaited. This function should only be called after a call to + * initiated. This function should only be called after a call to * `ble_gap_preempt()`. */ diff --git a/nimble/host/src/ble_gap_priv.h b/nimble/host/src/ble_gap_priv.h index a789f3976..0a566c61e 100644 --- a/nimble/host/src/ble_gap_priv.h +++ b/nimble/host/src/ble_gap_priv.h @@ -110,6 +110,7 @@ int ble_gap_repeat_pairing_event(const struct ble_gap_repeat_pairing *rp); int ble_gap_master_in_progress(void); void ble_gap_preempt(void); +void ble_gap_ensure_preempted(void); void ble_gap_preempt_done(void); void ble_gap_conn_broken(uint16_t conn_handle, int reason); diff --git a/nimble/host/src/ble_hs.c b/nimble/host/src/ble_hs.c index edfd88f05..3ee17575c 100644 --- a/nimble/host/src/ble_hs.c +++ b/nimble/host/src/ble_hs.c @@ -57,6 +57,7 @@ static struct ble_npl_event ble_hs_ev_start_stage1; static struct ble_npl_event ble_hs_ev_start_stage2; uint8_t ble_hs_sync_state; +uint8_t ble_hs_enabled_state; static int ble_hs_reset_reason; #define BLE_HS_SYNC_RETRY_TIMEOUT_MS 100 /* ms */ @@ -67,7 +68,7 @@ static void *ble_hs_parent_task; * Handles unresponsive timeouts and periodic retries in case of resource * shortage. */ -static struct ble_npl_callout ble_hs_timer_timer; +static struct ble_npl_callout ble_hs_timer; /* Shared queue that the host uses for work items. */ static struct ble_npl_eventq *ble_hs_evq; @@ -306,6 +307,12 @@ ble_hs_clear_rx_queue(void) } } +int +ble_hs_enabled(void) +{ + return ble_hs_enabled_state == BLE_HS_ENABLED_STATE_ON; +} + int ble_hs_synced(void) { @@ -399,25 +406,35 @@ ble_hs_timer_exp(struct ble_npl_event *ev) { int32_t ticks_until_next; - if (!ble_hs_sync_state) { + switch (ble_hs_sync_state) { + case BLE_HS_SYNC_STATE_GOOD: + ticks_until_next = ble_gattc_timer(); + ble_hs_timer_sched(ticks_until_next); + + ticks_until_next = ble_gap_timer(); + ble_hs_timer_sched(ticks_until_next); + + ticks_until_next = ble_l2cap_sig_timer(); + ble_hs_timer_sched(ticks_until_next); + + ticks_until_next = ble_sm_timer(); + ble_hs_timer_sched(ticks_until_next); + + ticks_until_next = ble_hs_conn_timer(); + ble_hs_timer_sched(ticks_until_next); + break; + + case BLE_HS_SYNC_STATE_BAD: ble_hs_reset(); - return; + break; + + case BLE_HS_SYNC_STATE_BRINGUP: + default: + /* The timer should not be set in this state. */ + assert(0); + break; } - ticks_until_next = ble_gattc_timer(); - ble_hs_timer_sched(ticks_until_next); - - ticks_until_next = ble_gap_timer(); - ble_hs_timer_sched(ticks_until_next); - - ticks_until_next = ble_l2cap_sig_timer(); - ble_hs_timer_sched(ticks_until_next); - - ticks_until_next = ble_sm_timer(); - ble_hs_timer_sched(ticks_until_next); - - ticks_until_next = ble_hs_conn_timer(); - ble_hs_timer_sched(ticks_until_next); } static void @@ -425,8 +442,12 @@ ble_hs_timer_reset(uint32_t ticks) { int rc; - rc = ble_npl_callout_reset(&ble_hs_timer_timer, ticks); - BLE_HS_DBG_ASSERT_EVAL(rc == 0); + if (!ble_hs_enabled()) { + ble_npl_callout_stop(&ble_hs_timer); + } else { + rc = ble_npl_callout_reset(&ble_hs_timer, ticks); + BLE_HS_DBG_ASSERT_EVAL(rc == 0); + } } static void @@ -442,9 +463,9 @@ ble_hs_timer_sched(int32_t ticks_from_now) * sooner than the previous expiration time. */ abs_time = ble_npl_time_get() + ticks_from_now; - if (!ble_npl_callout_is_active(&ble_hs_timer_timer) || + if (!ble_npl_callout_is_active(&ble_hs_timer) || ((ble_npl_stime_t)(abs_time - - ble_npl_callout_get_ticks(&ble_hs_timer_timer))) < 0) { + ble_npl_callout_get_ticks(&ble_hs_timer))) < 0) { ble_hs_timer_reset(ticks_from_now); } } @@ -595,15 +616,40 @@ ble_hs_start(void) { int rc; + ble_hs_lock(); + switch (ble_hs_enabled_state) { + case BLE_HS_ENABLED_STATE_ON: + rc = BLE_HS_EALREADY; + break; + + case BLE_HS_ENABLED_STATE_STOPPING: + rc = BLE_HS_EBUSY; + break; + + case BLE_HS_ENABLED_STATE_OFF: + ble_hs_enabled_state = BLE_HS_ENABLED_STATE_ON; + rc = 0; + break; + + default: + assert(0); + rc = BLE_HS_EUNKNOWN; + break; + } + ble_hs_unlock(); + + if (rc != 0) { + return rc; + } + ble_hs_parent_task = ble_npl_get_current_task_id(); /* Stop the timer just in case the host was already running (e.g., unit * tests). */ - ble_npl_callout_stop(&ble_hs_timer_timer); + ble_npl_callout_stop(&ble_hs_timer); - ble_npl_callout_init(&ble_hs_timer_timer, ble_hs_evq, - ble_hs_timer_exp, NULL); + ble_npl_callout_init(&ble_hs_timer, ble_hs_evq, ble_hs_timer_exp, NULL); rc = ble_gatts_start(); if (rc != 0) { @@ -681,6 +727,7 @@ ble_hs_init(void) * bss. */ ble_hs_reset_reason = 0; + ble_hs_enabled_state = BLE_HS_ENABLED_STATE_OFF; ble_npl_event_init(&ble_hs_ev_tx_notifications, ble_hs_event_tx_notify, NULL); @@ -718,6 +765,8 @@ ble_hs_init(void) rc = ble_gatts_init(); SYSINIT_PANIC_ASSERT(rc == 0); + ble_hs_stop_init(); + ble_mqueue_init(&ble_hs_rx_q, ble_hs_event_rx_data, NULL); rc = stats_init_and_reg( diff --git a/nimble/host/src/ble_hs_priv.h b/nimble/host/src/ble_hs_priv.h index 81e9519f2..f7d3cd43a 100644 --- a/nimble/host/src/ble_hs_priv.h +++ b/nimble/host/src/ble_hs_priv.h @@ -60,6 +60,10 @@ struct os_event; #define BLE_HS_SYNC_STATE_BRINGUP 1 #define BLE_HS_SYNC_STATE_GOOD 2 +#define BLE_HS_ENABLED_STATE_OFF 0 +#define BLE_HS_ENABLED_STATE_STOPPING 1 +#define BLE_HS_ENABLED_STATE_ON 2 + #if NIMBLE_BLE_CONNECT #define BLE_HS_MAX_CONNECTIONS MYNEWT_VAL(BLE_MAX_CONNECTIONS) #else @@ -90,6 +94,7 @@ extern STATS_SECT_DECL(ble_hs_stats) ble_hs_stats; extern struct os_mbuf_pool ble_hs_mbuf_pool; extern uint8_t ble_hs_sync_state; +extern uint8_t ble_hs_enabled_state; extern const uint8_t ble_hs_misc_null_addr[6]; @@ -125,6 +130,7 @@ void ble_hs_hw_error(uint8_t hw_code); void ble_hs_timer_resched(void); void ble_hs_notifications_sched(void); struct ble_npl_eventq *ble_hs_evq_get(void); +void ble_hs_stop_init(void); struct ble_mqueue { STAILQ_HEAD(, os_mbuf_pkthdr) head; diff --git a/nimble/host/src/ble_hs_stop.c b/nimble/host/src/ble_hs_stop.c new file mode 100644 index 000000000..2c46188eb --- /dev/null +++ b/nimble/host/src/ble_hs_stop.c @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include "sysinit/sysinit.h" +#include "syscfg/syscfg.h" +#include "ble_hs_priv.h" + +static ble_npl_event_fn ble_hs_stop_term_event_cb; +static struct ble_npl_event ble_hs_stop_term_ev; + +static struct ble_gap_event_listener ble_hs_stop_gap_listener; + +/** + * List of stop listeners. These are notified when a stop procedure completes. + */ +SLIST_HEAD(ble_hs_stop_listener_slist, ble_hs_stop_listener); +static struct ble_hs_stop_listener_slist ble_hs_stop_listeners; + +/** + * Called when a stop procedure has completed. + */ +static void +ble_hs_stop_done(int status) +{ + struct ble_hs_stop_listener_slist slist; + struct ble_hs_stop_listener *listener; + + ble_hs_lock(); + + ble_gap_event_listener_unregister(&ble_hs_stop_gap_listener); + + slist = ble_hs_stop_listeners; + SLIST_INIT(&ble_hs_stop_listeners); + + ble_hs_enabled_state = BLE_HS_ENABLED_STATE_OFF; + + ble_hs_unlock(); + + SLIST_FOREACH(listener, &slist, link) { + listener->fn(status, listener->arg); + } +} + +/** + * Terminates the first open connection. + * + * If there are no open connections, signals completion of the close procedure. + */ +static void +ble_hs_stop_terminate_next_conn(void) +{ + uint16_t handle; + int rc; + + handle = ble_hs_atomic_first_conn_handle(); + if (handle == BLE_HS_CONN_HANDLE_NONE) { + /* No open connections. Signal completion of the stop procedure. */ + ble_hs_stop_done(0); + return; + } + + rc = ble_gap_terminate(handle, BLE_ERR_REM_USER_CONN_TERM); + if (rc == 0) { + /* Terminate procedure successfully initiated. Let the GAP event + * handler deal with the result. + */ + } else { + BLE_HS_LOG(ERROR, + "ble_hs_stop: failed to terminate connection; rc=%d\n", rc); + ble_hs_stop_done(rc); + } +} + +/** + * Event handler. Attempts to terminate the first open connection if there is + * one. All additional connections are terminated elsewhere in the GAP event + * handler. + * + * If there are no connections, signals completion of the stop procedure. + */ +static void +ble_hs_stop_term_event_cb(struct ble_npl_event *ev) +{ + ble_hs_stop_terminate_next_conn(); +} + +/** + * GAP event callback. Listens for connection termination and then terminates + * the next one. + * + * If there are no connections, signals completion of the stop procedure. + */ +static int +ble_hs_stop_gap_event(struct ble_gap_event *event, void *arg) +{ + /* Only process connection termination events. */ + if (event->type == BLE_GAP_EVENT_DISCONNECT || + event->type == BLE_GAP_EVENT_TERM_FAILURE) { + + ble_hs_stop_terminate_next_conn(); + } + + return 0; +} + +/** + * Registers a listener to listen for completion of the current stop procedure. + */ +static void +ble_hs_stop_register_listener(struct ble_hs_stop_listener *listener, + ble_hs_stop_fn *fn, void *arg) +{ + BLE_HS_DBG_ASSERT(fn != NULL); + + listener->fn = fn; + listener->arg = arg; + SLIST_INSERT_HEAD(&ble_hs_stop_listeners, listener, link); +} + +static int +ble_hs_stop_begin(struct ble_hs_stop_listener *listener, + ble_hs_stop_fn *fn, void *arg) +{ + switch (ble_hs_enabled_state) { + case BLE_HS_ENABLED_STATE_ON: + /* Host is enabled; proceed with the stop procedure. */ + ble_hs_enabled_state = BLE_HS_ENABLED_STATE_STOPPING; + if (listener != NULL) { + ble_hs_stop_register_listener(listener, fn, arg); + } + + /* Put the host in the "stopping" state and ensure the host timer is + * not running. + */ + ble_hs_timer_resched(); + return 0; + + case BLE_HS_ENABLED_STATE_STOPPING: + /* A stop procedure is already in progress. Just listen for the + * procedure's completion. + */ + if (listener != NULL) { + ble_hs_stop_register_listener(listener, fn, arg); + } + return BLE_HS_EBUSY; + + case BLE_HS_ENABLED_STATE_OFF: + /* Host already stopped. */ + return BLE_HS_EALREADY; + + default: + assert(0); + return BLE_HS_EUNKNOWN; + } +} + +int +ble_hs_stop(struct ble_hs_stop_listener *listener, + ble_hs_stop_fn *fn, void *arg) +{ + int rc; + + ble_hs_lock(); + rc = ble_hs_stop_begin(listener, fn, arg); + ble_hs_unlock(); + + switch (rc) { + case 0: + break; + + case BLE_HS_EBUSY: + return 0; + + default: + return rc; + } + + /* Abort all active GAP procedures. */ + ble_gap_ensure_preempted(); + ble_gap_preempt_done(); + + rc = ble_gap_event_listener_register(&ble_hs_stop_gap_listener, + ble_hs_stop_gap_event, NULL); + if (rc != 0) { + return rc; + } + + /* Schedule termination of all open connections in the host task. This is + * done even if there are no open connections so that the result of the + * stop procedure is signaled in a consistent manner (asynchronously). + */ + ble_npl_eventq_put(ble_hs_evq_get(), &ble_hs_stop_term_ev); + + return 0; +} + +void +ble_hs_stop_init(void) +{ + ble_npl_event_init(&ble_hs_stop_term_ev, ble_hs_stop_term_event_cb, NULL); +} diff --git a/nimble/host/test/src/ble_hs_pvcy_test.c b/nimble/host/test/src/ble_hs_pvcy_test.c index 4a8bf6238..0bf982ad5 100644 --- a/nimble/host/test/src/ble_hs_pvcy_test.c +++ b/nimble/host/test/src/ble_hs_pvcy_test.c @@ -136,6 +136,7 @@ ble_hs_pvcy_test_util_start_host(int num_expected_irks) ble_hs_pvcy_test_util_add_irk_set_acks(false, false); } + ble_hs_enabled_state = BLE_HS_ENABLED_STATE_OFF; rc = ble_hs_start(); TEST_ASSERT_FATAL(rc == 0); diff --git a/nimble/host/test/src/ble_hs_stop_test.c b/nimble/host/test/src/ble_hs_stop_test.c new file mode 100644 index 000000000..2be0151e3 --- /dev/null +++ b/nimble/host/test/src/ble_hs_stop_test.c @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include "testutil/testutil.h" +#include "host/ble_hs.h" +#include "host/ble_hs_test.h" +#include "ble_hs_test_util.h" + +#define BHST_MAX_EVENTS 32 + +static struct ble_gap_event bhst_events[BHST_MAX_EVENTS]; +static int bhst_num_events; + +static struct ble_hs_stop_listener bhst_listener; +static struct os_sem bhst_sem; + +static int +bhst_gap_event(struct ble_gap_event *event, void *arg) +{ + TEST_ASSERT_FATAL(bhst_num_events < BHST_MAX_EVENTS); + + bhst_events[bhst_num_events++] = *event; + return 0; +} + +static void +bhst_stop_cb(int status, void *arg) +{ + int rc; + + rc = os_sem_release(&bhst_sem); + TEST_ASSERT_FATAL(rc == 0); +} + +TEST_CASE_TASK(ble_hs_stop_test_new_procs) +{ + static const struct ble_gap_disc_params disc_params; + static const struct ble_gap_adv_params adv_params; + + static const ble_addr_t peer_addr = { + BLE_ADDR_PUBLIC, + { 1, 2, 3, 4, 5, 6 } + }; + + int rc; + + rc = os_sem_init(&bhst_sem, 0); + TEST_ASSERT_FATAL(rc == 0); + + /* Stop the host and wait for the stop procedure to complete. */ + ble_hs_test_util_hci_ack_set( + BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_ADV_ENABLE), 0); + + rc = ble_hs_stop(&bhst_listener, bhst_stop_cb, NULL); + TEST_ASSERT_FATAL(rc == 0); + rc = os_sem_pend(&bhst_sem, OS_TIMEOUT_NEVER); + TEST_ASSERT_FATAL(rc == 0); + + /*** Ensure all GAP procedures fail. */ + + /* Advertise. */ + rc = ble_hs_test_util_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, &adv_params, + BLE_HS_FOREVER, bhst_gap_event, NULL, + 0, 0); + TEST_ASSERT(rc == BLE_HS_EDISABLED); + + /* Discover. */ + rc = ble_hs_test_util_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, + &disc_params, bhst_gap_event, NULL, 0, 0); + TEST_ASSERT(rc == BLE_HS_EDISABLED); + + /* Connect. */ + rc = ble_hs_test_util_connect(BLE_OWN_ADDR_PUBLIC, &peer_addr, + BLE_HS_FOREVER, NULL, + bhst_gap_event, NULL, 0); + TEST_ASSERT(rc == BLE_HS_EDISABLED); + + /*** Restart stack; ensure GAP procedures succeed. */ + + ble_hs_test_util_hci_ack_set_startup(); + ble_hs_sched_start(); + + /* Advertise. */ + rc = ble_hs_test_util_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, &adv_params, + BLE_HS_FOREVER, bhst_gap_event, NULL, + 0, 0); + TEST_ASSERT(rc == 0); + + rc = ble_hs_test_util_adv_stop(0); + TEST_ASSERT(rc == 0); + + /* Discover. */ + rc = ble_hs_test_util_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, + &disc_params, bhst_gap_event, NULL, 0, 0); + TEST_ASSERT(rc == 0); + + rc = ble_hs_test_util_disc_cancel(0); + TEST_ASSERT(rc == 0); + + /* Connect. */ + rc = ble_hs_test_util_connect(BLE_OWN_ADDR_PUBLIC, &peer_addr, + BLE_HS_FOREVER, NULL, + bhst_gap_event, NULL, 0); + TEST_ASSERT(rc == 0); + + rc = ble_hs_test_util_conn_cancel(0); + TEST_ASSERT(rc == 0); +} + +TEST_CASE_TASK(ble_hs_stop_test_cur_procs) +{ + static const struct ble_gap_disc_params disc_params; + static const struct ble_gap_adv_params adv_params; + + int rc; + + rc = os_sem_init(&bhst_sem, 0); + TEST_ASSERT_FATAL(rc == 0); + + /* Advertise. */ + rc = ble_hs_test_util_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, &adv_params, + BLE_HS_FOREVER, bhst_gap_event, NULL, + 0, 0); + TEST_ASSERT(rc == 0); + + /* Discover. */ + rc = ble_hs_test_util_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, + &disc_params, bhst_gap_event, NULL, 0, 0); + TEST_ASSERT(rc == 0); + + /* Preload the host with HCI acks for the cancel commands that will be sent + * automatically when the host stops. + */ + ble_hs_test_util_hci_ack_set( + BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_ADV_ENABLE), + 0); + ble_hs_test_util_hci_ack_append( + ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE, + BLE_HCI_OCF_LE_SET_SCAN_ENABLE), + 0); + + /* Stop the host and wait for the stop procedure to complete. */ + bhst_num_events = 0; + rc = ble_hs_stop(&bhst_listener, bhst_stop_cb, NULL); + TEST_ASSERT_FATAL(rc == 0); + rc = os_sem_pend(&bhst_sem, OS_TIMEOUT_NEVER); + TEST_ASSERT_FATAL(rc == 0); + + /* Ensure the GAP procedure cancellations were reported. */ + TEST_ASSERT_FATAL(bhst_num_events == 2); + TEST_ASSERT(bhst_events[0].type == BLE_GAP_EVENT_ADV_COMPLETE); + TEST_ASSERT(bhst_events[0].adv_complete.reason == BLE_HS_EPREEMPTED); + TEST_ASSERT(bhst_events[1].type == BLE_GAP_EVENT_DISC_COMPLETE); + TEST_ASSERT(bhst_events[1].disc_complete.reason == BLE_HS_EPREEMPTED); +} + +static void +bhst_pre_test(void *arg) +{ + ble_hs_test_util_init_no_sysinit_no_start(); + + /* Preload the host with HCI acks for the startup sequence. */ + ble_hs_test_util_hci_ack_set_startup(); +} + +TEST_SUITE(ble_hs_stop_test_suite) +{ + tu_suite_set_post_test_cb(ble_hs_test_util_post_test, NULL); + tu_suite_set_pre_test_cb(bhst_pre_test, NULL); + + ble_hs_stop_test_new_procs(); + ble_hs_stop_test_cur_procs(); +} + +int +ble_stop_test_all(void) +{ + ble_hs_stop_test_suite(); + + return tu_any_failed; +} diff --git a/nimble/host/test/src/ble_hs_test.c b/nimble/host/test/src/ble_hs_test.c index 021b268b7..db7bfb89b 100644 --- a/nimble/host/test/src/ble_hs_test.c +++ b/nimble/host/test/src/ble_hs_test.c @@ -53,6 +53,7 @@ main(int argc, char **argv) ble_l2cap_test_all(); ble_os_test_all(); ble_sm_test_all(); + ble_stop_test_all(); ble_store_test_all(); ble_uuid_test_all(); diff --git a/nimble/host/test/src/ble_hs_test_util.c b/nimble/host/test/src/ble_hs_test_util.c index afd7a7d41..b2fef75aa 100644 --- a/nimble/host/test/src/ble_hs_test_util.c +++ b/nimble/host/test/src/ble_hs_test_util.c @@ -1991,10 +1991,8 @@ ble_hs_test_util_reg_svcs(const struct ble_gatt_svc_def *svcs, void -ble_hs_test_util_init_no_start(void) +ble_hs_test_util_init_no_sysinit_no_start(void) { - sysinit(); - STAILQ_INIT(&ble_hs_test_util_prev_tx_queue); ble_hs_test_util_prev_tx_cur = NULL; @@ -2005,6 +2003,8 @@ ble_hs_test_util_init_no_start(void) ble_hs_test_util_hci_ack_set_startup(); + ble_hs_enabled_state = BLE_HS_ENABLED_STATE_OFF; + ble_hs_max_services = 16; ble_hs_max_client_configs = 32; ble_hs_max_attrs = 64; @@ -2018,6 +2018,13 @@ ble_hs_test_util_init_no_start(void) ble_store_clear(); } +void +ble_hs_test_util_init_no_start(void) +{ + sysinit(); + ble_hs_test_util_init_no_sysinit_no_start(); +} + void ble_hs_test_util_init(void) { diff --git a/nimble/host/test/src/ble_hs_test_util.h b/nimble/host/test/src/ble_hs_test_util.h index 2640adf69..cb5d14809 100644 --- a/nimble/host/test/src/ble_hs_test_util.h +++ b/nimble/host/test/src/ble_hs_test_util.h @@ -275,6 +275,7 @@ void ble_hs_test_util_reg_svcs(const struct ble_gatt_svc_def *svcs, ble_gatt_register_fn *reg_cb, void *cb_arg); void ble_hs_test_util_init_no_start(void); +void ble_hs_test_util_init_no_sysinit_no_start(void); void ble_hs_test_util_init(void); #ifdef __cplusplus diff --git a/porting/nimble/Makefile.defs b/porting/nimble/Makefile.defs index f5396aaf4..d3c3cd690 100644 --- a/porting/nimble/Makefile.defs +++ b/porting/nimble/Makefile.defs @@ -64,6 +64,7 @@ NIMBLE_SRC := \ $(NIMBLE_ROOT)/nimble/host/src/ble_hs_misc.c \ $(NIMBLE_ROOT)/nimble/host/src/ble_hs_pvcy.c \ $(NIMBLE_ROOT)/nimble/host/src/ble_hs_startup.c \ + $(NIMBLE_ROOT)/nimble/host/src/ble_hs_stop.c \ $(NIMBLE_ROOT)/nimble/host/src/ble_ibeacon.c \ $(NIMBLE_ROOT)/nimble/host/src/ble_l2cap.c \ $(NIMBLE_ROOT)/nimble/host/src/ble_l2cap_coc.c \