diff options
Diffstat (limited to 'src/libs/mynewt-nimble/nimble/host/src/ble_hs_stop.c')
-rw-r--r-- | src/libs/mynewt-nimble/nimble/host/src/ble_hs_stop.c | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/src/libs/mynewt-nimble/nimble/host/src/ble_hs_stop.c b/src/libs/mynewt-nimble/nimble/host/src/ble_hs_stop.c new file mode 100644 index 00000000..b90d3ec6 --- /dev/null +++ b/src/libs/mynewt-nimble/nimble/host/src/ble_hs_stop.c @@ -0,0 +1,283 @@ +/* + * 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 <assert.h> +#include "sysinit/sysinit.h" +#include "syscfg/syscfg.h" +#include "ble_hs_priv.h" +#include "nimble/nimble_npl.h" +#ifndef MYNEWT +#include "nimble/nimble_port.h" +#endif + +#define BLE_HOST_STOP_TIMEOUT_MS MYNEWT_VAL(BLE_HS_STOP_ON_SHUTDOWN_TIMEOUT) + +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; + +/* Track number of connections */ +static uint8_t ble_hs_stop_conn_cnt; + +static struct ble_npl_callout ble_hs_stop_terminate_tmo; + +/** + * 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_npl_callout_stop(&ble_hs_stop_terminate_tmo); + + 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); + } +} + +#if MYNEWT_VAL(BLE_PERIODIC_ADV) +/** + * Terminates all active periodic sync handles + * + * If there are no active periodic sync handles, signals completion of the + * close procedure. + */ +static int +ble_hs_stop_terminate_all_periodic_sync(void) +{ + int rc = 0; + struct ble_hs_periodic_sync *psync; + uint16_t sync_handle; + + while((psync = ble_hs_periodic_sync_first())){ + /* Terminate sync command waits a command complete event, so there + * is no need to wait for GAP event, as the calling thread will be + * blocked on the hci semaphore until the command complete is received. + * + * Also, once the sync is terminated, the psync will be freed and + * removed from the list such that the next call to + * ble_hs_periodic_sync_first yields the next psync handle + */ + sync_handle = psync->sync_handle; + rc = ble_gap_periodic_adv_sync_terminate(sync_handle); + if (rc != 0 && rc != BLE_HS_ENOTCONN) { + BLE_HS_LOG(ERROR, "failed to terminate periodic sync=0x%04x, rc=%d\n", + sync_handle, rc); + return rc; + } + } + + return 0; +} +#endif + +/** + * Terminates connection. + */ +static int +ble_hs_stop_terminate_conn(struct ble_hs_conn *conn, void *arg) +{ + int rc; + + rc = ble_gap_terminate_with_conn(conn, BLE_ERR_REM_USER_CONN_TERM); + if (rc == 0) { + /* Terminate procedure successfully initiated. Let the GAP event + * handler deal with the result. + */ + ble_hs_stop_conn_cnt++; + } else { + /* If failed, just make sure we are not going to wait for connection complete event, + * just count it as already disconnected + */ + BLE_HS_LOG(ERROR, "ble_hs_stop: failed to terminate connection; rc=%d\n", rc); + } + + return 0; +} + +/** + * This is called when host graceful disconnect timeout fires. That means some devices + * are out of range and disconnection completed did no happen yet. + */ +static void +ble_hs_stop_terminate_timeout_cb(struct ble_npl_event *ev) +{ + BLE_HS_LOG(ERROR, "ble_hs_stop_terminate_timeout_cb," + "%d connection(s) still up \n", ble_hs_stop_conn_cnt); + + /* TODO: Shall we send error here? */ + ble_hs_stop_done(0); +} + +/** + * 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_conn_cnt--; + + if (ble_hs_stop_conn_cnt == 0) { + ble_hs_stop_done(0); + } + } + + 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_preempt(); + ble_gap_preempt_done(); + +#if MYNEWT_VAL(BLE_PERIODIC_ADV) + /* Check for active periodic sync first and terminate it all */ + rc = ble_hs_stop_terminate_all_periodic_sync(); + if (rc != 0) { + return rc; + } +#endif + + rc = ble_gap_event_listener_register(&ble_hs_stop_gap_listener, + ble_hs_stop_gap_event, NULL); + if (rc != 0) { + return rc; + } + + ble_hs_lock(); + ble_hs_conn_foreach(ble_hs_stop_terminate_conn, NULL); + ble_hs_unlock(); + + if (ble_hs_stop_conn_cnt > 0) { + ble_npl_callout_reset(&ble_hs_stop_terminate_tmo, + ble_npl_time_ms_to_ticks32(BLE_HOST_STOP_TIMEOUT_MS)); + } else { + /* No connections, stop is completed */ + ble_hs_stop_done(0); + } + + return 0; +} + +void +ble_hs_stop_init(void) +{ +#ifdef MYNEWT + ble_npl_callout_init(&ble_hs_stop_terminate_tmo, ble_npl_eventq_dflt_get(), + ble_hs_stop_terminate_timeout_cb, NULL); +#else + ble_npl_callout_init(&ble_hs_stop_terminate_tmo, nimble_port_get_dflt_eventq(), + ble_hs_stop_terminate_timeout_cb, NULL); +#endif +} |