summaryrefslogtreecommitdiff
path: root/src/libs/mynewt-nimble/nimble/host/src/ble_hs_stop.c
diff options
context:
space:
mode:
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.c283
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
+}