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 }