• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *  http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 #include <assert.h>
21 #include "sysinit/sysinit.h"
22 #include "syscfg/syscfg.h"
23 #include "ble_hs_priv.h"
24 #include "nimble/nimble_npl.h"
25 #ifndef MYNEWT
26 #include "nimble/nimble_port.h"
27 #endif
28 
29 #define BLE_HOST_STOP_TIMEOUT_MS MYNEWT_VAL(BLE_HS_STOP_ON_SHUTDOWN_TIMEOUT)
30 
31 static struct ble_gap_event_listener ble_hs_stop_gap_listener;
32 
33 /**
34  * List of stop listeners.  These are notified when a stop procedure completes.
35  */
36 SLIST_HEAD(ble_hs_stop_listener_slist, ble_hs_stop_listener);
37 static struct ble_hs_stop_listener_slist ble_hs_stop_listeners;
38 
39 /* Track number of connections */
40 static uint8_t ble_hs_stop_conn_cnt;
41 
42 static struct ble_npl_callout ble_hs_stop_terminate_tmo;
43 
44 /**
45  * Called when a stop procedure has completed.
46  */
ble_hs_stop_done(int status)47 static void ble_hs_stop_done(int status)
48 {
49     struct ble_hs_stop_listener_slist slist;
50     struct ble_hs_stop_listener *listener;
51     ble_npl_callout_stop(&ble_hs_stop_terminate_tmo);
52     ble_hs_lock();
53     ble_gap_event_listener_unregister(&ble_hs_stop_gap_listener);
54     slist = ble_hs_stop_listeners;
55     SLIST_INIT(&ble_hs_stop_listeners);
56     ble_hs_enabled_state = BLE_HS_ENABLED_STATE_OFF;
57     ble_hs_unlock();
58     SLIST_FOREACH(listener, &slist, link) {
59         listener->fn(status, listener->arg);
60     }
61 }
62 
63 #if MYNEWT_VAL(BLE_PERIODIC_ADV)
64 /**
65  * Terminates all active periodic sync handles
66  *
67  * If there are no active periodic sync handles, signals completion of the
68  * close procedure.
69  */
ble_hs_stop_terminate_all_periodic_sync(void)70 static int ble_hs_stop_terminate_all_periodic_sync(void)
71 {
72     int rc = 0;
73     struct ble_hs_periodic_sync *psync;
74     uint16_t sync_handle;
75 
76     while ((psync = ble_hs_periodic_sync_first())) {
77         /* Terminate sync command waits a command complete event, so there
78          * is no need to wait for GAP event, as the calling thread will be
79          * blocked on the hci semaphore until the command complete is received.
80          *
81          * Also, once the sync is terminated, the psync will be freed and
82          * removed from the list such that the next call to
83          * ble_hs_periodic_sync_first yields the next psync handle
84          */
85         sync_handle = psync->sync_handle;
86         rc = ble_gap_periodic_adv_sync_terminate(sync_handle);
87         if (rc != 0 && rc != BLE_HS_ENOTCONN) {
88             BLE_HS_LOG(ERROR, "failed to terminate periodic sync=0x%04x, rc=%d\n",
89                        sync_handle, rc);
90             return rc;
91         }
92     }
93 
94     return 0;
95 }
96 #endif
97 
98 /**
99  * Terminates connection.
100  */
ble_hs_stop_terminate_conn(struct ble_hs_conn * conn,void * arg)101 static int ble_hs_stop_terminate_conn(struct ble_hs_conn *conn, void *arg)
102 {
103     int rc;
104     rc = ble_gap_terminate_with_conn(conn, BLE_ERR_REM_USER_CONN_TERM);
105     if (rc == 0) {
106         /* Terminate procedure successfully initiated.  Let the GAP event
107          * handler deal with the result.
108          */
109         ble_hs_stop_conn_cnt++;
110     } else {
111         /* If failed, just make sure we are not going to wait for connection complete event,
112          * just count it as already disconnected
113          */
114         BLE_HS_LOG(ERROR, "ble_hs_stop: failed to terminate connection; rc=%d\n", rc);
115     }
116 
117     return 0;
118 }
119 
120 /**
121  * This is called when host graceful disconnect timeout fires. That means some devices
122  * are out of range and disconnection completed did no happen yet.
123  */
ble_hs_stop_terminate_timeout_cb(struct ble_npl_event * ev)124 static void ble_hs_stop_terminate_timeout_cb(struct ble_npl_event *ev)
125 {
126     BLE_HS_LOG(ERROR, "ble_hs_stop_terminate_timeout_cb,"
127                "%d connection(s) still up \n", ble_hs_stop_conn_cnt);
128     ble_hs_stop_done(0);
129 }
130 
131 /**
132  * GAP event callback.  Listens for connection termination and then terminates
133  * the next one.
134  *
135  * If there are no connections, signals completion of the stop procedure.
136  */
ble_hs_stop_gap_event(struct ble_gap_event * event,void * arg)137 static int ble_hs_stop_gap_event(struct ble_gap_event *event, void *arg)
138 {
139     /* Only process connection termination events. */
140     if (event->type == BLE_GAP_EVENT_DISCONNECT ||
141             event->type == BLE_GAP_EVENT_TERM_FAILURE) {
142         ble_hs_stop_conn_cnt--;
143         if (ble_hs_stop_conn_cnt == 0) {
144             ble_hs_stop_done(0);
145         }
146     }
147 
148     return 0;
149 }
150 
151 /**
152  * Registers a listener to listen for completion of the current stop procedure.
153  */
ble_hs_stop_register_listener(struct ble_hs_stop_listener * listener,ble_hs_stop_fn * fn,void * arg)154 static void ble_hs_stop_register_listener(struct ble_hs_stop_listener *listener,
155                                           ble_hs_stop_fn *fn, void *arg)
156 {
157     BLE_HS_DBG_ASSERT(fn != NULL);
158     listener->fn = fn;
159     listener->arg = arg;
160     SLIST_INSERT_HEAD(&ble_hs_stop_listeners, listener, link);
161 }
162 
ble_hs_stop_begin(struct ble_hs_stop_listener * listener,ble_hs_stop_fn * fn,void * arg)163 static int ble_hs_stop_begin(struct ble_hs_stop_listener *listener,
164                              ble_hs_stop_fn *fn, void *arg)
165 {
166     switch (ble_hs_enabled_state) {
167         case BLE_HS_ENABLED_STATE_ON:
168             /* Host is enabled; proceed with the stop procedure. */
169             ble_hs_enabled_state = BLE_HS_ENABLED_STATE_STOPPING;
170 
171             if (listener != NULL) {
172                 ble_hs_stop_register_listener(listener, fn, arg);
173             }
174 
175             /* Put the host in the "stopping" state and ensure the host timer is
176              * not running.
177              */
178             ble_hs_timer_resched();
179             return 0;
180 
181         case BLE_HS_ENABLED_STATE_STOPPING:
182 
183             /* A stop procedure is already in progress.  Just listen for the
184              * procedure's completion.
185              */
186             if (listener != NULL) {
187                 ble_hs_stop_register_listener(listener, fn, arg);
188             }
189 
190             return BLE_HS_EBUSY;
191 
192         case BLE_HS_ENABLED_STATE_OFF:
193             /* Host already stopped. */
194             return BLE_HS_EALREADY;
195 
196         default:
197             assert(0);
198             return BLE_HS_EUNKNOWN;
199     }
200 }
201 
ble_hs_stop(struct ble_hs_stop_listener * listener,ble_hs_stop_fn * fn,void * arg)202 int ble_hs_stop(struct ble_hs_stop_listener *listener, ble_hs_stop_fn *fn, void *arg)
203 {
204     int rc;
205     ble_hs_lock();
206     rc = ble_hs_stop_begin(listener, fn, arg);
207     ble_hs_unlock();
208 
209     switch (rc) {
210         case 0:
211             break;
212 
213         case BLE_HS_EBUSY:
214             return 0;
215 
216         default:
217             return rc;
218     }
219 
220     /* Abort all active GAP procedures. */
221     ble_gap_preempt();
222     ble_gap_preempt_done();
223 #if MYNEWT_VAL(BLE_PERIODIC_ADV)
224     /* Check for active periodic sync first and terminate it all */
225     rc = ble_hs_stop_terminate_all_periodic_sync();
226     if (rc != 0) {
227         return rc;
228     }
229 
230 #endif
231     rc = ble_gap_event_listener_register(&ble_hs_stop_gap_listener,
232                                          ble_hs_stop_gap_event, NULL);
233     if (rc != 0) {
234         return rc;
235     }
236 
237     ble_hs_lock();
238     ble_hs_conn_foreach(ble_hs_stop_terminate_conn, NULL);
239     ble_hs_unlock();
240 
241     if (ble_hs_stop_conn_cnt > 0) {
242         ble_npl_callout_reset(&ble_hs_stop_terminate_tmo,
243                               ble_npl_time_ms_to_ticks32(BLE_HOST_STOP_TIMEOUT_MS));
244     } else {
245         /* No connections, stop is completed */
246         ble_hs_stop_done(0);
247     }
248 
249     return 0;
250 }
251 
ble_hs_stop_init(void)252 void ble_hs_stop_init(void)
253 {
254 #ifdef MYNEWT
255     ble_npl_callout_init(&ble_hs_stop_terminate_tmo, ble_npl_eventq_dflt_get(),
256                          ble_hs_stop_terminate_timeout_cb, NULL);
257 #else
258     ble_npl_callout_init(&ble_hs_stop_terminate_tmo, nimble_port_get_dflt_eventq(),
259                          ble_hs_stop_terminate_timeout_cb, NULL);
260 #endif
261 }
ble_hs_stop_deinit(void)262 void ble_hs_stop_deinit(void)
263 {
264 #ifdef MYNEWT
265     ble_npl_callout_deinit(&ble_hs_stop_terminate_tmo);
266 #else
267     ble_npl_callout_deinit(&ble_hs_stop_terminate_tmo);
268 #endif
269 }