/* * 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 "nimble_syscfg.h" #include "ble_hs_priv.h" #include "host/ble_hs_stop.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 }