xref: /nrf52832-nimble/packages/NimBLE-latest/nimble/host/src/ble_hs_stop.c (revision 042d53a763ad75cb1465103098bb88c245d95138)
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