blob: 31480d77e98533d2e623df76c709c64acbcea322 [file] [log] [blame]
/*
* 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 <assert.h>
#include "sysinit/sysinit.h"
#include "syscfg/syscfg.h"
#include "ble_hs_priv.h"
static ble_npl_event_fn ble_hs_stop_term_event_cb;
static struct ble_npl_event ble_hs_stop_term_ev;
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;
/**
* 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_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);
}
}
/**
* Terminates the first open connection.
*
* If there are no open connections, signals completion of the close procedure.
*/
static void
ble_hs_stop_terminate_next_conn(void)
{
uint16_t handle;
int rc;
handle = ble_hs_atomic_first_conn_handle();
if (handle == BLE_HS_CONN_HANDLE_NONE) {
/* No open connections. Signal completion of the stop procedure. */
ble_hs_stop_done(0);
return;
}
rc = ble_gap_terminate(handle, BLE_ERR_REM_USER_CONN_TERM);
if (rc == 0) {
/* Terminate procedure successfully initiated. Let the GAP event
* handler deal with the result.
*/
} else {
BLE_HS_LOG(ERROR,
"ble_hs_stop: failed to terminate connection; rc=%d\n", rc);
ble_hs_stop_done(rc);
}
}
/**
* Event handler. Attempts to terminate the first open connection if there is
* one. All additional connections are terminated elsewhere in the GAP event
* handler.
*
* If there are no connections, signals completion of the stop procedure.
*/
static void
ble_hs_stop_term_event_cb(struct ble_npl_event *ev)
{
ble_hs_stop_terminate_next_conn();
}
/**
* 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_terminate_next_conn();
}
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();
rc = ble_gap_event_listener_register(&ble_hs_stop_gap_listener,
ble_hs_stop_gap_event, NULL);
if (rc != 0) {
return rc;
}
/* Schedule termination of all open connections in the host task. This is
* done even if there are no open connections so that the result of the
* stop procedure is signaled in a consistent manner (asynchronously).
*/
ble_npl_eventq_put(ble_hs_evq_get(), &ble_hs_stop_term_ev);
return 0;
}
void
ble_hs_stop_init(void)
{
ble_npl_event_init(&ble_hs_stop_term_ev, ble_hs_stop_term_event_cb, NULL);
}