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
25 static ble_npl_event_fn ble_hs_stop_term_event_cb;
26 static struct ble_npl_event ble_hs_stop_term_ev;
27
28 static struct ble_gap_event_listener ble_hs_stop_gap_listener;
29
30 /**
31 * List of stop listeners. These are notified when a stop procedure completes.
32 */
33 SLIST_HEAD(ble_hs_stop_listener_slist, ble_hs_stop_listener);
34 static struct ble_hs_stop_listener_slist ble_hs_stop_listeners;
35
36 /**
37 * Called when a stop procedure has completed.
38 */
39 static void
ble_hs_stop_done(int status)40 ble_hs_stop_done(int status)
41 {
42 struct ble_hs_stop_listener_slist slist;
43 struct ble_hs_stop_listener *listener;
44
45 ble_hs_lock();
46
47 ble_gap_event_listener_unregister(&ble_hs_stop_gap_listener);
48
49 slist = ble_hs_stop_listeners;
50 SLIST_INIT(&ble_hs_stop_listeners);
51
52 ble_hs_enabled_state = BLE_HS_ENABLED_STATE_OFF;
53
54 ble_hs_unlock();
55
56 SLIST_FOREACH(listener, &slist, link) {
57 listener->fn(status, listener->arg);
58 }
59 }
60
61 /**
62 * Terminates the first open connection.
63 *
64 * If there are no open connections, signals completion of the close procedure.
65 */
66 static void
ble_hs_stop_terminate_next_conn(void)67 ble_hs_stop_terminate_next_conn(void)
68 {
69 uint16_t handle;
70 int rc;
71
72 handle = ble_hs_atomic_first_conn_handle();
73 if (handle == BLE_HS_CONN_HANDLE_NONE) {
74 /* No open connections. Signal completion of the stop procedure. */
75 ble_hs_stop_done(0);
76 return;
77 }
78
79 rc = ble_gap_terminate(handle, BLE_ERR_REM_USER_CONN_TERM);
80 if (rc == 0) {
81 /* Terminate procedure successfully initiated. Let the GAP event
82 * handler deal with the result.
83 */
84 } else {
85 BLE_HS_LOG(ERROR,
86 "ble_hs_stop: failed to terminate connection; rc=%d\n", rc);
87 ble_hs_stop_done(rc);
88 }
89 }
90
91 /**
92 * Event handler. Attempts to terminate the first open connection if there is
93 * one. All additional connections are terminated elsewhere in the GAP event
94 * handler.
95 *
96 * If there are no connections, signals completion of the stop procedure.
97 */
98 static void
ble_hs_stop_term_event_cb(struct ble_npl_event * ev)99 ble_hs_stop_term_event_cb(struct ble_npl_event *ev)
100 {
101 ble_hs_stop_terminate_next_conn();
102 }
103
104 /**
105 * GAP event callback. Listens for connection termination and then terminates
106 * the next one.
107 *
108 * If there are no connections, signals completion of the stop procedure.
109 */
110 static int
ble_hs_stop_gap_event(struct ble_gap_event * event,void * arg)111 ble_hs_stop_gap_event(struct ble_gap_event *event, void *arg)
112 {
113 /* Only process connection termination events. */
114 if (event->type == BLE_GAP_EVENT_DISCONNECT ||
115 event->type == BLE_GAP_EVENT_TERM_FAILURE) {
116
117 ble_hs_stop_terminate_next_conn();
118 }
119
120 return 0;
121 }
122
123 /**
124 * Registers a listener to listen for completion of the current stop procedure.
125 */
126 static void
ble_hs_stop_register_listener(struct ble_hs_stop_listener * listener,ble_hs_stop_fn * fn,void * arg)127 ble_hs_stop_register_listener(struct ble_hs_stop_listener *listener,
128 ble_hs_stop_fn *fn, void *arg)
129 {
130 BLE_HS_DBG_ASSERT(fn != NULL);
131
132 listener->fn = fn;
133 listener->arg = arg;
134 SLIST_INSERT_HEAD(&ble_hs_stop_listeners, listener, link);
135 }
136
137 static int
ble_hs_stop_begin(struct ble_hs_stop_listener * listener,ble_hs_stop_fn * fn,void * arg)138 ble_hs_stop_begin(struct ble_hs_stop_listener *listener,
139 ble_hs_stop_fn *fn, void *arg)
140 {
141 switch (ble_hs_enabled_state) {
142 case BLE_HS_ENABLED_STATE_ON:
143 /* Host is enabled; proceed with the stop procedure. */
144 ble_hs_enabled_state = BLE_HS_ENABLED_STATE_STOPPING;
145 if (listener != NULL) {
146 ble_hs_stop_register_listener(listener, fn, arg);
147 }
148
149 /* Put the host in the "stopping" state and ensure the host timer is
150 * not running.
151 */
152 ble_hs_timer_resched();
153 return 0;
154
155 case BLE_HS_ENABLED_STATE_STOPPING:
156 /* A stop procedure is already in progress. Just listen for the
157 * procedure's completion.
158 */
159 if (listener != NULL) {
160 ble_hs_stop_register_listener(listener, fn, arg);
161 }
162 return BLE_HS_EBUSY;
163
164 case BLE_HS_ENABLED_STATE_OFF:
165 /* Host already stopped. */
166 return BLE_HS_EALREADY;
167
168 default:
169 assert(0);
170 return BLE_HS_EUNKNOWN;
171 }
172 }
173
174 int
ble_hs_stop(struct ble_hs_stop_listener * listener,ble_hs_stop_fn * fn,void * arg)175 ble_hs_stop(struct ble_hs_stop_listener *listener,
176 ble_hs_stop_fn *fn, void *arg)
177 {
178 int rc;
179
180 ble_hs_lock();
181 rc = ble_hs_stop_begin(listener, fn, arg);
182 ble_hs_unlock();
183
184 switch (rc) {
185 case 0:
186 break;
187
188 case BLE_HS_EBUSY:
189 return 0;
190
191 default:
192 return rc;
193 }
194
195 /* Abort all active GAP procedures. */
196 ble_gap_preempt();
197 ble_gap_preempt_done();
198
199 rc = ble_gap_event_listener_register(&ble_hs_stop_gap_listener,
200 ble_hs_stop_gap_event, NULL);
201 if (rc != 0) {
202 return rc;
203 }
204
205 /* Schedule termination of all open connections in the host task. This is
206 * done even if there are no open connections so that the result of the
207 * stop procedure is signaled in a consistent manner (asynchronously).
208 */
209 ble_npl_eventq_put(ble_hs_evq_get(), &ble_hs_stop_term_ev);
210
211 return 0;
212 }
213
214 void
ble_hs_stop_init(void)215 ble_hs_stop_init(void)
216 {
217 ble_npl_event_init(&ble_hs_stop_term_ev, ble_hs_stop_term_event_cb, NULL);
218 }
219