| /* |
| * 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_terminate_sync(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 |
| } |