blob: 091e597d327e69bbc5c25072aae6b801278b090f [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 <string.h>
#include <errno.h>
#include "bsp/bsp.h"
#include "os/os.h"
#include "ble_hs_priv.h"
#include "host/host_hci.h"
#include "ble_hs_priv.h"
#include "ble_hs_adv_priv.h"
#include "ble_hs_conn.h"
#include "ble_hci_sched.h"
#include "ble_gatt_priv.h"
#include "ble_gap_priv.h"
#define BLE_GAP_OP_NULL 0
#define BLE_GAP_STATE_NULL 255
#define BLE_GAP_OP_M_DISC 1
#define BLE_GAP_OP_M_CONN 2
#define BLE_GAP_OP_S_NON 1
#define BLE_GAP_OP_S_UND 2
#define BLE_GAP_OP_S_DIR 3
#define BLE_GAP_OP_W_SET 1
/** Discovery master states. */
#define BLE_GAP_STATE_M_DISC_PARAMS 0
#define BLE_GAP_STATE_M_DISC_ENABLE 1
#define BLE_GAP_STATE_M_DISC_ACKED 2
#define BLE_GAP_STATE_M_DISC_DISABLE 3
/** Connect master states. */
#define BLE_GAP_STATE_M_PENDING 0
#define BLE_GAP_STATE_M_UNACKED 1
#define BLE_GAP_STATE_M_ACKED 2
/** Undirected slave states. */
#define BLE_GAP_STATE_S_UND_PARAMS 0
#define BLE_GAP_STATE_S_UND_POWER 1
#define BLE_GAP_STATE_S_UND_ADV_DATA 2
#define BLE_GAP_STATE_S_UND_RSP_DATA 3
#define BLE_GAP_STATE_S_UND_ENABLE 4
#define BLE_GAP_STATE_S_UND_ADV 5
/** Directed slave states. */
#define BLE_GAP_STATE_S_DIR_PARAMS 0
#define BLE_GAP_STATE_S_DIR_ENABLE 1
#define BLE_GAP_STATE_S_DIR_ADV 2
/** White list states. */
#define BLE_GAP_STATE_W_CLEAR 0
#define BLE_GAP_STATE_W_ADD 1
/** Connection update states. */
#define BLE_GAP_STATE_U_UPDATE 0
#define BLE_GAP_STATE_U_UPDATE_ACKED 1
#define BLE_GAP_STATE_U_REPLY 2
#define BLE_GAP_STATE_U_REPLY_ACKED 3
#define BLE_GAP_STATE_U_NEG_REPLY 4
/**
* The maximum amount of user data that can be put into the advertising data.
* Six bytes are reserved at the end for the flags field and the transmit power
* field.
*/
#define BLE_GAP_ADV_DATA_LIMIT (BLE_HCI_MAX_ADV_DATA_LEN - 6)
static const struct ble_gap_crt_params ble_gap_params_dflt = {
.scan_itvl = 0x0010,
.scan_window = 0x0010,
.itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN,
.itvl_max = BLE_GAP_INITIAL_CONN_ITVL_MAX,
.latency = BLE_GAP_INITIAL_CONN_LATENCY,
.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT,
.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN,
};
static const struct hci_adv_params ble_gap_adv_params_dflt = {
.adv_itvl_min = 0,
.adv_itvl_max = 0,
.adv_type = BLE_HCI_ADV_TYPE_ADV_IND,
.own_addr_type = BLE_HCI_ADV_OWN_ADDR_PUBLIC,
.peer_addr_type = BLE_HCI_ADV_PEER_ADDR_PUBLIC,
.adv_channel_map = BLE_HCI_ADV_CHANMASK_DEF,
.adv_filter_policy = BLE_HCI_ADV_FILT_DEF,
};
/**
* The state of the in-progress master connection. If no master connection is
* currently in progress, then the op field is set to BLE_GAP_OP_NULL.
*/
static bssnz_t struct {
uint8_t op;
uint8_t state;
uint8_t hci_handle;
union {
struct {
uint8_t addr_type;
uint8_t addr[6];
struct ble_gap_crt_params params;
ble_gap_conn_fn *cb;
void *cb_arg;
} conn;
struct {
uint8_t disc_mode;
uint8_t filter_policy;
uint8_t scan_type;
ble_gap_disc_fn *cb;
void *cb_arg;
} disc;
};
} ble_gap_master;
/**
* The state of the in-progress slave connection. If no slave connection is
* currently in progress, then the op field is set to BLE_GAP_OP_NULL.
*/
static bssnz_t struct {
uint8_t op;
uint8_t state;
uint8_t disc_mode;
uint8_t hci_handle;
ble_gap_conn_fn *cb;
void *cb_arg;
uint8_t dir_addr_type;
uint8_t dir_addr[BLE_DEV_ADDR_LEN];
struct hci_adv_params adv_params;
int8_t tx_pwr_lvl;
uint8_t adv_data_len;
uint8_t adv_data[BLE_HCI_MAX_ADV_DATA_LEN];
} ble_gap_slave;
static bssnz_t struct {
ble_gap_wl_fn *cb;
void *cb_arg;
struct ble_gap_white_entry *entries;
uint8_t op;
uint8_t state;
uint8_t count;
uint8_t cur;
uint8_t hci_handle;
} ble_gap_wl;
struct ble_gap_update_entry {
SLIST_ENTRY(ble_gap_update_entry) next;
struct ble_gap_upd_params params;
uint16_t conn_handle;
uint8_t state;
uint8_t hci_handle;
};
static SLIST_HEAD(, ble_gap_update_entry) ble_gap_update_entries;
static void *ble_gap_update_mem;
static struct os_mempool ble_gap_update_pool;
static int ble_gap_adv_params_tx(void *arg);
static int ble_gap_adv_power_tx(void *arg);
static int ble_gap_adv_data_tx(void *arg);
static int ble_gap_adv_rsp_data_tx(void *arg);
static int ble_gap_adv_enable_tx(void *arg);
static int ble_gap_wl_tx_add(void *arg);
static int ble_gap_disc_tx_disable(void *arg);
static ble_hci_sched_tx_fn * const ble_gap_dispatch_adv_und[] = {
[BLE_GAP_STATE_S_UND_PARAMS] = ble_gap_adv_params_tx,
[BLE_GAP_STATE_S_UND_POWER] = ble_gap_adv_power_tx,
[BLE_GAP_STATE_S_UND_ADV_DATA] = ble_gap_adv_data_tx,
[BLE_GAP_STATE_S_UND_RSP_DATA] = ble_gap_adv_rsp_data_tx,
[BLE_GAP_STATE_S_UND_ENABLE] = ble_gap_adv_enable_tx,
[BLE_GAP_STATE_S_UND_ADV] = NULL,
};
static ble_hci_sched_tx_fn * const ble_gap_dispatch_adv_dir[] = {
[BLE_GAP_STATE_S_DIR_PARAMS] = ble_gap_adv_params_tx,
[BLE_GAP_STATE_S_DIR_ENABLE] = ble_gap_adv_enable_tx,
[BLE_GAP_STATE_S_DIR_ADV] = NULL,
};
static struct os_callout_func ble_gap_master_timer;
static struct os_callout_func ble_gap_slave_timer;
struct ble_gap_snapshot {
struct ble_gap_conn_desc desc;
ble_gap_conn_fn *cb;
void *cb_arg;
};
static struct os_mutex ble_gap_mutex;
/*****************************************************************************
* $mutex *
*****************************************************************************/
static void
ble_gap_lock(void)
{
struct os_task *owner;
int rc;
owner = ble_gap_mutex.mu_owner;
if (owner != NULL) {
assert(owner != os_sched_get_current_task());
}
rc = os_mutex_pend(&ble_gap_mutex, 0xffffffff);
assert(rc == 0 || rc == OS_NOT_STARTED);
}
static void
ble_gap_unlock(void)
{
int rc;
rc = os_mutex_release(&ble_gap_mutex);
assert(rc == 0 || rc == OS_NOT_STARTED);
}
int
ble_gap_locked_by_cur_task(void)
{
struct os_task *owner;
owner = ble_gap_mutex.mu_owner;
return owner != NULL && owner == os_sched_get_current_task();
}
/*****************************************************************************
* $snapshot *
*****************************************************************************/
/**
* Lock restrictions: None.
*/
static void
ble_gap_fill_conn_desc(struct ble_hs_conn *conn,
struct ble_gap_conn_desc *desc)
{
desc->conn_handle = conn->bhc_handle;
desc->peer_addr_type = conn->bhc_addr_type;
memcpy(desc->peer_addr, conn->bhc_addr, sizeof desc->peer_addr);
desc->conn_itvl = conn->bhc_itvl;
desc->conn_latency = conn->bhc_latency;
desc->supervision_timeout = conn->bhc_supervision_timeout;
}
/**
* Lock restrictions: None.
*/
static void
ble_gap_conn_to_snapshot(struct ble_hs_conn *conn,
struct ble_gap_snapshot *snap)
{
ble_gap_fill_conn_desc(conn, &snap->desc);
snap->cb = conn->bhc_cb;
snap->cb_arg = conn->bhc_cb_arg;
}
/**
* Lock restrictions:
* o Caller unlocks ble_hs_conn.
*/
static int
ble_gap_find_snapshot(uint16_t handle, struct ble_gap_snapshot *snap)
{
struct ble_hs_conn *conn;
ble_hs_conn_lock();
conn = ble_hs_conn_find(handle);
if (conn != NULL) {
ble_gap_conn_to_snapshot(conn, snap);
}
ble_hs_conn_unlock();
if (conn == NULL) {
return BLE_HS_ENOENT;
} else {
return 0;
}
}
/*****************************************************************************
* $misc *
*****************************************************************************/
/**
* Lock restrictions:
* o Caller locks gap.
*/
static void
ble_gap_master_reset_state(void)
{
os_callout_stop(&ble_gap_master_timer.cf_c);
ble_gap_master.op = BLE_GAP_OP_NULL;
}
/**
* Lock restrictions:
* o Caller locks gap.
*/
static void
ble_gap_slave_reset_state(void)
{
os_callout_stop(&ble_gap_slave_timer.cf_c);
ble_gap_slave.op = BLE_GAP_OP_NULL;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_call_conn_cb(int event, int status,
struct ble_gap_snapshot *snap,
struct ble_gap_upd_params *self_params,
struct ble_gap_upd_params *peer_params)
{
struct ble_gap_conn_ctxt ctxt;
int rc;
ble_hs_misc_assert_no_locks();
memset(&ctxt, 0, sizeof ctxt);
ctxt.desc = &snap->desc;
ctxt.self_params = self_params;
ctxt.peer_params = peer_params;
if (snap->cb != NULL) {
rc = snap->cb(event, status, &ctxt, snap->cb_arg);
} else {
if (event == BLE_GAP_EVENT_CONN_UPDATE_REQ) {
/* Just copy peer parameters back into reply. */
*self_params = *peer_params;
}
rc = 0;
}
return rc;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_call_slave_cb(int event, int status, int reset_state)
{
struct ble_gap_conn_ctxt ctxt;
struct ble_gap_conn_desc desc;
ble_gap_conn_fn *cb;
void *cb_arg;
ble_hs_misc_assert_no_locks();
ble_gap_lock();
desc.conn_handle = BLE_HS_CONN_HANDLE_NONE;
desc.peer_addr_type = ble_gap_slave.dir_addr_type;
memcpy(desc.peer_addr, ble_gap_slave.dir_addr, sizeof desc.peer_addr);
cb = ble_gap_slave.cb;
cb_arg = ble_gap_slave.cb_arg;
if (reset_state) {
ble_gap_slave_reset_state();
}
ble_gap_unlock();
if (cb != NULL) {
ctxt.desc = &desc;
ctxt.peer_params = NULL;
ctxt.self_params = NULL;
cb(event, status, &ctxt, cb_arg);
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_call_master_conn_cb(int event, int status, int reset_state)
{
struct ble_gap_conn_ctxt ctxt;
struct ble_gap_conn_desc desc;
ble_gap_conn_fn *cb;
void *cb_arg;
int rc;
ble_hs_misc_assert_no_locks();
ble_gap_lock();
memset(&desc, 0, sizeof ctxt);
desc.conn_handle = BLE_HS_CONN_HANDLE_NONE;
desc.peer_addr_type = ble_gap_master.conn.addr_type;
memcpy(desc.peer_addr, ble_gap_master.conn.addr,
sizeof desc.peer_addr);
cb = ble_gap_master.conn.cb;
cb_arg = ble_gap_master.conn.cb_arg;
if (reset_state) {
ble_gap_master_reset_state();
}
ble_gap_unlock();
if (cb != NULL) {
ctxt.desc = &desc;
ctxt.peer_params = NULL;
ctxt.self_params = NULL;
rc = cb(event, status, &ctxt, cb_arg);
} else {
rc = 0;
}
return rc;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_call_master_disc_cb(int event, int status, struct ble_hs_adv *adv,
struct ble_hs_adv_fields *fields, int reset_state)
{
struct ble_gap_disc_desc desc;
ble_gap_disc_fn *cb;
void *cb_arg;
ble_hs_misc_assert_no_locks();
ble_gap_lock();
if (adv != NULL) {
desc.event_type = adv->event_type;
desc.addr_type = adv->addr_type;
desc.length_data = adv->length_data;
desc.rssi = adv->rssi;
memcpy(desc.addr, adv->addr, sizeof adv->addr);
desc.data = adv->data;
desc.fields = fields;
} else {
memset(&desc, 0, sizeof desc);
}
cb = ble_gap_master.disc.cb;
cb_arg = ble_gap_master.disc.cb_arg;
if (reset_state) {
ble_gap_master_reset_state();
}
ble_gap_unlock();
if (cb != NULL) {
cb(event, status, &desc, cb_arg);
}
}
/**
* Lock restrictions: Caller must NOT lock ble_hs_conn mutex.
*/
static void
ble_gap_call_wl_cb(int status, int reset_state)
{
ble_gap_wl_fn *cb;
void *cb_arg;
ble_gap_lock();
cb = ble_gap_wl.cb;
cb_arg = ble_gap_wl.cb_arg;
if (reset_state) {
ble_gap_wl.op = BLE_GAP_OP_NULL;
}
ble_gap_unlock();
if (cb != NULL) {
cb(status, cb_arg);
}
}
/**
* Lock restrictions:
* o Caller locks gap.
*/
static struct ble_gap_update_entry *
ble_gap_update_find(uint16_t conn_handle)
{
struct ble_gap_update_entry *entry;
SLIST_FOREACH(entry, &ble_gap_update_entries, next) {
if (entry->conn_handle == conn_handle) {
return entry;
}
}
return NULL;
}
/**
* Lock restrictions: None.
*/
static struct ble_gap_update_entry *
ble_gap_update_entry_alloc(uint16_t conn_handle,
struct ble_gap_upd_params *params, int state)
{
struct ble_gap_update_entry *entry;
#ifdef BLE_HS_DEBUG
ble_gap_lock();
assert(ble_gap_update_find(conn_handle) == NULL);
ble_gap_unlock();
#endif
entry = os_memblock_get(&ble_gap_update_pool);
if (entry == NULL) {
return NULL;
}
memset(entry, 0, sizeof *entry);
entry->conn_handle = conn_handle;
entry->params = *params;
entry->state = state;
return entry;
}
/**
* Lock restrictions: None.
*/
static void
ble_gap_update_entry_free(struct ble_gap_update_entry *entry)
{
int rc;
rc = os_memblock_put(&ble_gap_update_pool, entry);
assert(rc == 0);
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_update_notify(struct ble_gap_update_entry *entry, int status)
{
struct ble_gap_snapshot snap;
int rc;
rc = ble_gap_find_snapshot(entry->conn_handle, &snap);
if (rc != 0) {
return;
}
ble_gap_call_conn_cb(BLE_GAP_EVENT_CONN_UPDATED, status, &snap, NULL,
NULL);
}
/**
* Called when an error is encountered while the master-connection-fsm is
* active. Resets the state machine, clears the HCI ack callback, and notifies
* the host task that the next hci_batch item can be processed.
*
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_master_failed(int status)
{
switch (ble_gap_master.op) {
case BLE_GAP_OP_M_DISC:
ble_gap_call_master_disc_cb(BLE_GAP_EVENT_DISC_FINISHED, status,
NULL, NULL, 1);
break;
case BLE_GAP_OP_M_CONN:
ble_gap_call_master_conn_cb(BLE_GAP_EVENT_CONN, status, 1);
break;
default:
break;
}
}
/**
* Lock restrictions: None.
*/
static void
ble_gap_update_entry_remove_free(struct ble_gap_update_entry *entry)
{
SLIST_REMOVE(&ble_gap_update_entries, entry,
ble_gap_update_entry, next);
ble_gap_update_entry_free(entry);
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_update_failed(struct ble_gap_update_entry *entry, int status)
{
ble_gap_update_notify(entry, status);
ble_gap_update_entry_free(entry);
}
/**
* Lock restrictions:
* o Caller unlocks gap.
*/
static void
ble_gap_conn_broken(uint16_t conn_handle)
{
struct ble_gap_update_entry *entry;
ble_gap_lock();
entry = ble_gap_update_find(conn_handle);
if (entry != NULL) {
if (entry->hci_handle != BLE_HCI_SCHED_HANDLE_NONE) {
ble_hci_sched_cancel(entry->hci_handle);
}
ble_gap_update_entry_remove_free(entry);
}
ble_gap_unlock();
ble_gattc_connection_broken(conn_handle);
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
void
ble_gap_rx_disconn_complete(struct hci_disconn_complete *evt)
{
#if !NIMBLE_OPT_CONNECT
return;
#endif
struct ble_gap_snapshot snap;
struct ble_hs_conn *conn;
int rc;
ble_hs_misc_assert_no_locks();
if (evt->status == 0) {
/* Find the connection that this event refers to. */
ble_hs_conn_lock();
conn = ble_hs_conn_find(evt->connection_handle);
if (conn != NULL) {
ble_gap_conn_to_snapshot(conn, &snap);
ble_gap_conn_broken(evt->connection_handle);
ble_hs_conn_remove(conn);
ble_hs_conn_free(conn);
}
ble_hs_conn_unlock();
if (conn != NULL) {
ble_gap_call_conn_cb(BLE_GAP_EVENT_CONN, BLE_HS_ENOTCONN,
&snap, NULL, NULL);
}
} else {
rc = ble_gap_find_snapshot(evt->connection_handle, &snap);
if (rc == 0) {
ble_gap_call_conn_cb(BLE_GAP_EVENT_TERM_FAILURE,
BLE_HS_HCI_ERR(evt->status), &snap,
NULL, NULL);
}
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
void
ble_gap_rx_update_complete(struct hci_le_conn_upd_complete *evt)
{
#if !NIMBLE_OPT_CONNECT
return;
#endif
struct ble_gap_update_entry *entry;
struct ble_gap_snapshot snap;
struct ble_hs_conn *conn;
ble_hs_misc_assert_no_locks();
ble_hs_conn_lock();
conn = ble_hs_conn_find(evt->connection_handle);
if (conn != NULL) {
ble_gap_lock();
entry = ble_gap_update_find(evt->connection_handle);
if (entry != NULL) {
ble_gap_update_entry_remove_free(entry);
}
ble_gap_unlock();
if (evt->status == 0) {
conn->bhc_itvl = evt->conn_itvl;
conn->bhc_latency = evt->conn_latency;
conn->bhc_supervision_timeout = evt->supervision_timeout;
}
ble_gap_conn_to_snapshot(conn, &snap);
}
ble_hs_conn_unlock();
if (conn != NULL) {
ble_gap_call_conn_cb(BLE_GAP_EVENT_CONN_UPDATED,
BLE_HS_HCI_ERR(evt->status), &snap, NULL, NULL);
}
}
/**
* Tells you if the BLE host is in the process of creating a master connection.
*
* Lock restrictions: None.
*/
int
ble_gap_master_in_progress(void)
{
return ble_gap_master.op != BLE_GAP_OP_NULL;
}
/**
* Tells you if the BLE host is in the process of creating a slave connection.
*
* Lock restrictions: None.
*/
int
ble_gap_slave_in_progress(void)
{
return ble_gap_slave.op != BLE_GAP_OP_NULL;
}
/**
* Tells you if the BLE host is in the process of updating a connection.
*
* Lock restrictions:
* o Caller unlocks gap.
*
* @param conn_handle The connection to test, or
* BLE_HS_CONN_HANDLE_NONE to check all
* connections.
*
* @return 0=connection not being updated;
* 1=connection being updated.
*/
int
ble_gap_update_in_progress(uint16_t conn_handle)
{
#if !NIMBLE_OPT_CONNECT
return BLE_HS_ENOTSUP;
#endif
struct ble_gap_update_entry *entry;
ble_gap_lock();
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
entry = ble_gap_update_find(conn_handle);
} else {
entry = SLIST_FIRST(&ble_gap_update_entries);
}
ble_gap_unlock();
return entry != NULL;
}
/**
* Lock restrictions:
* o Caller unlocks gap.
*/
static int
ble_gap_gen_set_op(uint8_t *slot, uint8_t op)
{
int rc;
ble_gap_lock();
if (*slot != BLE_GAP_OP_NULL) {
rc = BLE_HS_EALREADY;
} else {
*slot = op;
rc = 0;
}
ble_gap_unlock();
return rc;
}
/**
* Lock restrictions:
* o Caller unlocks gap.
*/
static int
ble_gap_master_set_op(uint8_t op)
{
return ble_gap_gen_set_op(&ble_gap_master.op, op);
}
/**
* Lock restrictions:
* o Caller unlocks gap.
*/
static int
ble_gap_slave_set_op(uint8_t op)
{
return ble_gap_gen_set_op(&ble_gap_slave.op, op);
}
/**
* Lock restrictions:
* o Caller unlocks gap.
*/
static int
ble_gap_wl_set_op(uint8_t op)
{
int rc;
ble_gap_lock();
if (ble_gap_wl_busy()) {
rc = BLE_HS_EBUSY;
} else {
ble_gap_wl.op = op;
rc = 0;
}
ble_gap_unlock();
return rc;
}
/**
* Lock restrictions: None.
*/
static int
ble_gap_currently_advertising(void)
{
switch (ble_gap_slave.op) {
case BLE_GAP_OP_NULL:
return 0;
case BLE_GAP_OP_S_NON:
return ble_gap_slave.state == BLE_GAP_STATE_S_UND_ADV;
case BLE_GAP_OP_S_UND:
return ble_gap_slave.state == BLE_GAP_STATE_S_UND_ADV;
case BLE_GAP_OP_S_DIR:
return ble_gap_slave.state == BLE_GAP_STATE_S_DIR_ADV;
default:
assert(0);
return 0;
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_master_enqueue(uint8_t state, int in_progress,
ble_hci_sched_tx_fn *hci_tx_cb, void *cb_arg)
{
int rc;
ble_hs_misc_assert_no_locks();
ble_gap_master.state = state;
rc = ble_hci_sched_enqueue(hci_tx_cb, cb_arg, &ble_gap_master.hci_handle);
if (rc != 0) {
if (in_progress) {
ble_gap_master_failed(rc);
} else {
ble_gap_master_reset_state();
}
}
return rc;
}
/**
* Attempts to complete the master connection process in response to a
* "connection complete" event from the controller. If the master connection
* FSM is in a state that can accept this event, and the peer device address is
* valid, the master FSM is reset and success is returned.
*
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*
* @param addr_type The address type of the peer; one of the
* following values:
* o BLE_HCI_ADV_PEER_ADDR_PUBLIC
* o BLE_HCI_ADV_PEER_ADDR_RANDOM
* @param addr The six-byte address of the connection peer.
*
* @return 0 if the connection complete event was
* accepted;
* BLE_HS_ENOENT if the event does not apply.
*/
static int
ble_gap_accept_master_conn(uint8_t addr_type, uint8_t *addr)
{
switch (ble_gap_master.op) {
case BLE_GAP_OP_NULL:
case BLE_GAP_OP_M_DISC:
return BLE_HS_ENOENT;
case BLE_GAP_OP_M_CONN:
if (ble_gap_master.state != BLE_GAP_STATE_M_ACKED) {
return BLE_HS_ENOENT;
}
if (ble_gap_master.conn.addr_type == BLE_GAP_ADDR_TYPE_WL ||
(addr_type == ble_gap_master.conn.addr_type &&
memcmp(addr, ble_gap_master.conn.addr,
BLE_DEV_ADDR_LEN) == 0)) {
return 0;
} else {
ble_gap_master_failed(BLE_HS_ECONTROLLER);
return BLE_HS_ECONTROLLER;
}
default:
assert(0);
return BLE_HS_ENOENT;
}
}
/**
* Attempts to complete the slave connection process in response to a
* "connection complete" event from the controller. If the slave connection
* FSM is in a state that can accept this event, and the peer device address is
* valid, the master FSM is reset and success is returned.
*
* Lock restrictions: None.
*
* @param addr_type The address type of the peer; one of the
* following values:
* o BLE_HCI_ADV_PEER_ADDR_PUBLIC
* o BLE_HCI_ADV_PEER_ADDR_RANDOM
* @param addr The six-byte address of the connection peer.
*
* @return 0 if the connection complete event was
* accepted;
* BLE_HS_ENOENT if the event does not apply.
*/
static int
ble_gap_accept_slave_conn(uint8_t addr_type, uint8_t *addr)
{
if (!ble_gap_currently_advertising()) {
return BLE_HS_ENOENT;
}
switch (ble_gap_slave.op) {
case BLE_GAP_OP_NULL:
case BLE_GAP_OP_S_NON:
return BLE_HS_ENOENT;
case BLE_GAP_OP_S_UND:
return 0;
case BLE_GAP_OP_S_DIR:
if (ble_gap_slave.dir_addr_type != addr_type ||
memcmp(ble_gap_slave.dir_addr, addr, BLE_DEV_ADDR_LEN) != 0) {
return BLE_HS_ENOENT;
} else {
return 0;
}
default:
assert(0);
return BLE_HS_ENOENT;
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
void
ble_gap_rx_adv_report(struct ble_hs_adv *adv)
{
#if !NIMBLE_OPT_ROLE_OBSERVER
return;
#endif
struct ble_hs_adv_fields fields;
int rc;
if (ble_gap_master.op != BLE_GAP_OP_M_DISC ||
ble_gap_master.state != BLE_GAP_STATE_M_DISC_ACKED) {
return;
}
rc = ble_hs_adv_parse_fields(&fields, adv->data, adv->length_data);
if (rc != 0) {
/* XXX: Increment stat. */
return;
}
if (ble_gap_master.disc.disc_mode == BLE_GAP_DISC_MODE_LTD &&
!(fields.flags & BLE_HS_ADV_F_DISC_LTD)) {
return;
}
ble_gap_call_master_disc_cb(BLE_GAP_EVENT_DISC_SUCCESS, 0, adv,
&fields, 0);
}
/**
* Processes an incoming connection-complete HCI event.
*
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
int
ble_gap_rx_conn_complete(struct hci_le_conn_complete *evt)
{
#if !NIMBLE_OPT_CONNECT
return BLE_HS_ENOTSUP;
#endif
struct ble_gap_snapshot snap;
struct ble_hs_conn *conn;
int status;
int rc;
/* Determine if this event refers to a completed connection or a connection
* in progress.
*/
ble_hs_conn_lock();
conn = ble_hs_conn_find(evt->connection_handle);
/* Apply the event to the existing connection if it exists. */
if (conn != NULL) {
/* XXX: Does this ever happen? */
if (evt->status != 0) {
ble_gap_conn_to_snapshot(conn, &snap);
ble_gap_conn_broken(evt->connection_handle);
ble_hs_conn_remove(conn);
ble_hs_conn_free(conn);
}
}
ble_hs_conn_unlock();
if (conn != NULL) {
if (evt->status != 0) {
ble_gap_call_conn_cb(BLE_GAP_EVENT_CONN, evt->status, &snap,
NULL, NULL);
}
return 0;
}
/* This event refers to a new connection. */
if (evt->status != BLE_ERR_SUCCESS) {
status = BLE_HS_HCI_ERR(evt->status);
/* Determine the role from the status code. */
switch (evt->status) {
case BLE_ERR_DIR_ADV_TMO:
if (ble_gap_slave_in_progress()) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FINISHED, 0, 1);
}
break;
default:
if (ble_gap_master_in_progress()) {
ble_gap_master_failed(status);
}
break;
}
return 0;
}
switch (evt->role) {
case BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER:
rc = ble_gap_accept_master_conn(evt->peer_addr_type,
evt->peer_addr);
if (rc != 0) {
return rc;
}
break;
case BLE_HCI_LE_CONN_COMPLETE_ROLE_SLAVE:
rc = ble_gap_accept_slave_conn(evt->peer_addr_type,
evt->peer_addr);
if (rc != 0) {
return rc;
}
break;
default:
assert(0);
break;
}
/* We verified that there is a free connection when the procedure began. */
conn = ble_hs_conn_alloc();
assert(conn != NULL);
conn->bhc_handle = evt->connection_handle;
memcpy(conn->bhc_addr, evt->peer_addr, sizeof conn->bhc_addr);
conn->bhc_itvl = evt->conn_itvl;
conn->bhc_latency = evt->conn_latency;
conn->bhc_supervision_timeout = evt->supervision_timeout;
if (evt->role == BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER) {
conn->bhc_flags |= BLE_HS_CONN_F_MASTER;
conn->bhc_cb = ble_gap_master.conn.cb;
conn->bhc_cb_arg = ble_gap_master.conn.cb_arg;
ble_gap_master_reset_state();
} else {
conn->bhc_cb = ble_gap_slave.cb;
conn->bhc_cb_arg = ble_gap_slave.cb_arg;
ble_gap_slave_reset_state();
}
ble_gap_conn_to_snapshot(conn, &snap);
ble_hs_conn_insert(conn);
ble_gap_call_conn_cb(BLE_GAP_EVENT_CONN, 0, &snap, NULL, NULL);
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
int
ble_gap_rx_l2cap_update_req(uint16_t conn_handle,
struct ble_gap_upd_params *params)
{
struct ble_gap_conn_ctxt ctxt;
struct ble_gap_snapshot snap;
int rc;
ble_hs_misc_assert_no_locks();
rc = ble_gap_find_snapshot(conn_handle, &snap);
if (rc != 0) {
return rc;
}
if (snap.cb != NULL) {
ctxt.desc = &snap.desc;
ctxt.peer_params = params;
ctxt.self_params = NULL;
rc = snap.cb(BLE_GAP_EVENT_L2CAP_UPDATE_REQ, 0, &ctxt, snap.cb_arg);
} else {
rc = 0;
}
return rc;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_master_timer_exp(void *arg)
{
ble_hs_misc_assert_no_locks();
assert(ble_gap_master_in_progress());
switch (ble_gap_master.op) {
case BLE_GAP_OP_M_DISC:
/* When a discovery procedure times out, it is not a failure. */
ble_gap_master_enqueue(BLE_GAP_STATE_M_DISC_DISABLE, 1,
ble_gap_disc_tx_disable, NULL);
break;
default:
ble_gap_master_failed(BLE_HS_ETIMEOUT);
break;
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_slave_timer_exp(void *arg)
{
ble_hs_misc_assert_no_locks();
assert(ble_gap_slave_in_progress());
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FINISHED, 0, 1);
}
/*****************************************************************************
* $white list *
*****************************************************************************/
/**
* XXX: This should be static, as it requires the private gap mutex to be
* locked. Currently only called by gap code and test code.
*
* Lock restrictions:
* o Caller locks gap.
*/
int
ble_gap_wl_busy(void)
{
#if !NIMBLE_OPT_WHITELIST
return BLE_HS_ENOTSUP;
#endif
/* Check if application is currently setting the white list. */
if (ble_gap_wl.op != BLE_GAP_OP_NULL) {
return 1;
}
/* Check if an auto or selective connection establishment procedure is in
* progress.
*/
if (ble_gap_master.op == BLE_GAP_OP_M_CONN &&
ble_gap_master.conn.addr_type == BLE_GAP_ADDR_TYPE_WL) {
return 1;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_wl_enqueue(uint8_t state, int in_progress,
ble_hci_sched_tx_fn *hci_tx_cb, void *cb_arg)
{
int rc;
ble_gap_wl.state = state;
rc = ble_hci_sched_enqueue(hci_tx_cb, cb_arg, &ble_gap_wl.hci_handle);
if (rc != 0) {
if (in_progress) {
ble_gap_call_wl_cb(rc, 1);
} else {
ble_gap_wl.op = BLE_GAP_OP_NULL;
}
}
return rc;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_wl_ack_add(struct ble_hci_ack *ack, void *arg)
{
assert(ble_gap_wl.op == BLE_GAP_OP_W_SET);
assert(ble_gap_wl.state == BLE_GAP_STATE_W_ADD);
ble_gap_wl.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
ble_gap_call_wl_cb(ack->bha_status, 1);
return;
}
ble_gap_wl.cur++;
if (ble_gap_wl.cur < ble_gap_wl.count) {
ble_gap_wl_enqueue(BLE_GAP_STATE_W_ADD, 1, ble_gap_wl_tx_add, NULL);
} else {
/* Success. */
ble_gap_call_wl_cb(0, 1);
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_wl_tx_add(void *arg)
{
struct ble_gap_white_entry *white_entry;
int rc;
assert(ble_gap_wl.op == BLE_GAP_OP_W_SET);
assert(ble_gap_wl.state == BLE_GAP_STATE_W_ADD);
assert(ble_gap_wl.entries != NULL);
assert(ble_gap_wl.cur < ble_gap_wl.count);
ble_hci_sched_set_ack_cb(ble_gap_wl_ack_add, NULL);
white_entry = ble_gap_wl.entries + ble_gap_wl.cur;
rc = host_hci_cmd_le_add_to_whitelist(white_entry->addr,
white_entry->addr_type);
if (rc != 0) {
ble_gap_call_wl_cb(rc, 1);
return 1;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_wl_ack_clear(struct ble_hci_ack *ack, void *arg)
{
assert(ble_gap_wl.op == BLE_GAP_OP_W_SET);
assert(ble_gap_wl.state == BLE_GAP_STATE_W_CLEAR);
ble_gap_wl.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
ble_gap_call_wl_cb(ack->bha_status, 1);
return;
}
ble_gap_wl_enqueue(BLE_GAP_STATE_W_ADD, 1, ble_gap_wl_tx_add, NULL);
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_wl_tx_clear(void *arg)
{
int rc;
assert(ble_gap_wl.op == BLE_GAP_OP_W_SET);
assert(ble_gap_wl.state == BLE_GAP_STATE_W_CLEAR);
ble_hci_sched_set_ack_cb(ble_gap_wl_ack_clear, NULL);
rc = host_hci_cmd_le_clear_whitelist();
if (rc != 0) {
ble_gap_call_wl_cb(rc, 1);
return 1;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
int
ble_gap_wl_set(struct ble_gap_white_entry *white_list,
uint8_t white_list_count, ble_gap_wl_fn *cb, void *cb_arg)
{
#if !NIMBLE_OPT_WHITELIST
return BLE_HS_ENOTSUP;
#endif
int rc;
int i;
if (white_list_count <= 0) {
return BLE_HS_EINVAL;
}
for (i = 0; i < white_list_count; i++) {
if (white_list[i].addr_type != BLE_ADDR_TYPE_PUBLIC &&
white_list[i].addr_type != BLE_ADDR_TYPE_RANDOM) {
return BLE_HS_EINVAL;
}
}
rc = ble_gap_wl_set_op(BLE_GAP_OP_W_SET);
if (rc != 0) {
return rc;
}
ble_gap_wl.cb = cb;
ble_gap_wl.cb_arg = cb_arg;
ble_gap_wl.entries = white_list;
ble_gap_wl.count = white_list_count;
ble_gap_wl.cur = 0;
rc = ble_gap_wl_enqueue(BLE_GAP_STATE_W_CLEAR, 0,
ble_gap_wl_tx_clear, NULL);
return rc;
}
/*****************************************************************************
* $stop advertise *
*****************************************************************************/
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_adv_ack_disable(struct ble_hci_ack *ack, void *arg)
{
ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status == 0) {
/* Advertising should now be aborted. */
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FINISHED, 0, 1);
} else {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_STOP_FAILURE,
ack->bha_status, 0);
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_adv_disable_tx(void *arg)
{
int rc;
ble_hci_sched_set_ack_cb(ble_gap_adv_ack_disable, NULL);
rc = host_hci_cmd_le_set_adv_enable(0);
if (rc != BLE_ERR_SUCCESS) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_STOP_FAILURE,
BLE_HS_HCI_ERR(rc), 0);
return 1;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
int
ble_gap_adv_stop(void)
{
#if !NIMBLE_OPT_ADVERTISE
return BLE_HS_ENOTSUP;
#endif
int rc;
/* Do nothing if advertising is already disabled. */
if (!ble_gap_currently_advertising()) {
return BLE_HS_EALREADY;
}
rc = ble_hci_sched_enqueue(ble_gap_adv_disable_tx, NULL,
&ble_gap_slave.hci_handle);
if (rc != 0) {
return rc;
}
return 0;
}
/*****************************************************************************
* $advertise *
*****************************************************************************/
/**
* Lock restrictions: None.
*/
static void
ble_gap_adv_itvls(uint8_t disc_mode, uint8_t conn_mode,
uint16_t *out_itvl_min, uint16_t *out_itvl_max)
{
switch (conn_mode) {
case BLE_GAP_CONN_MODE_NON:
*out_itvl_min = BLE_GAP_ADV_FAST_INTERVAL2_MIN;
*out_itvl_max = BLE_GAP_ADV_FAST_INTERVAL2_MAX;
break;
case BLE_GAP_CONN_MODE_UND:
*out_itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
*out_itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MAX;
break;
case BLE_GAP_CONN_MODE_DIR:
*out_itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
*out_itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MAX;
break;
default:
assert(0);
*out_itvl_min = BLE_GAP_ADV_FAST_INTERVAL2_MIN;
*out_itvl_max = BLE_GAP_ADV_FAST_INTERVAL2_MAX;
break;
}
}
/**
* Lock restrictions: None.
*/
static ble_hci_sched_tx_fn *
ble_gap_adv_get_dispatch(void)
{
switch (ble_gap_slave.op) {
case BLE_GAP_OP_S_NON:
return ble_gap_dispatch_adv_und[ble_gap_slave.state];
case BLE_GAP_OP_S_UND:
return ble_gap_dispatch_adv_und[ble_gap_slave.state];
case BLE_GAP_OP_S_DIR:
return ble_gap_dispatch_adv_dir[ble_gap_slave.state];
default:
assert(0);
return NULL;
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_adv_next_state(void)
{
ble_hci_sched_tx_fn *tx_fn;
int rc;
ble_gap_slave.state++;
tx_fn = ble_gap_adv_get_dispatch();
if (tx_fn != NULL) {
rc = ble_hci_sched_enqueue(tx_fn, NULL, &ble_gap_slave.hci_handle);
if (rc != 0) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE, rc, 1);
}
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_adv_ack(struct ble_hci_ack *ack, void *arg)
{
ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE, ack->bha_status, 1);
} else {
ble_gap_adv_next_state();
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_adv_enable_tx(void *arg)
{
int rc;
ble_hci_sched_set_ack_cb(ble_gap_adv_ack, NULL);
rc = host_hci_cmd_le_set_adv_enable(1);
if (rc != BLE_ERR_SUCCESS) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE,
BLE_HS_HCI_ERR(rc), 1);
return 1;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_adv_rsp_data_tx(void *arg)
{
uint8_t rsp_data[BLE_HCI_MAX_SCAN_RSP_DATA_LEN] = { 0 }; /* XXX */
int rc;
ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
ble_hci_sched_set_ack_cb(ble_gap_adv_ack, NULL);
rc = host_hci_cmd_le_set_scan_rsp_data(rsp_data, sizeof rsp_data);
if (rc != 0) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE,
BLE_HS_HCI_ERR(rc), 1);
return 1;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_adv_data_tx(void *arg)
{
uint8_t adv_data_len;
uint8_t flags;
int rc;
assert(ble_gap_slave.op != BLE_GAP_OP_NULL);
/* Calculate the value of the flags field from the discoverable mode. */
flags = 0;
switch (ble_gap_slave.disc_mode) {
case BLE_GAP_DISC_MODE_NON:
break;
case BLE_GAP_DISC_MODE_LTD:
flags |= BLE_HS_ADV_F_DISC_LTD;
break;
case BLE_GAP_DISC_MODE_GEN:
flags |= BLE_HS_ADV_F_DISC_GEN;
break;
default:
assert(0);
break;
}
flags |= BLE_HS_ADV_F_BREDR_UNSUP;
/* Encode the flags AD field if it is nonzero. */
adv_data_len = ble_gap_slave.adv_data_len;
if (flags != 0) {
rc = ble_hs_adv_set_flat(BLE_HS_ADV_TYPE_FLAGS, 1, &flags,
ble_gap_slave.adv_data, &adv_data_len,
BLE_HCI_MAX_ADV_DATA_LEN);
assert(rc == 0);
}
/* Encode the transmit power AD field. */
rc = ble_hs_adv_set_flat(BLE_HS_ADV_TYPE_TX_PWR_LVL, 1,
&ble_gap_slave.tx_pwr_lvl,
ble_gap_slave.adv_data,
&adv_data_len, BLE_HCI_MAX_ADV_DATA_LEN);
assert(rc == 0);
ble_hci_sched_set_ack_cb(ble_gap_adv_ack, NULL);
rc = host_hci_cmd_le_set_adv_data(ble_gap_slave.adv_data,
adv_data_len);
if (rc != 0) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE,
BLE_HS_HCI_ERR(rc), 1);
return 1;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_adv_power_ack(struct ble_hci_ack *ack, void *arg)
{
int8_t power_level;
ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE, ack->bha_status, 1);
return;
}
if (ack->bha_params_len != BLE_HCI_ADV_CHAN_TXPWR_ACK_PARAM_LEN) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE,
BLE_HS_ECONTROLLER, 1);
return;
}
power_level = ack->bha_params[1];
if (power_level < BLE_HCI_ADV_CHAN_TXPWR_MIN ||
power_level > BLE_HCI_ADV_CHAN_TXPWR_MAX) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE,
BLE_HS_ECONTROLLER, 1);
return;
}
/* Save power level value so it can be put in the advertising data. */
ble_gap_slave.tx_pwr_lvl = power_level;
ble_gap_adv_next_state();
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_adv_power_tx(void *arg)
{
int rc;
ble_gap_slave.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
ble_hci_sched_set_ack_cb(ble_gap_adv_power_ack, NULL);
rc = host_hci_cmd_read_adv_pwr();
if (rc != 0) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE,
BLE_HS_HCI_ERR(rc), 1);
return 1;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_adv_params_tx(void *arg)
{
struct hci_adv_params hap;
int rc;
hap = ble_gap_slave.adv_params;
switch (ble_gap_slave.op) {
case BLE_GAP_OP_S_NON:
hap.adv_type = BLE_HCI_ADV_TYPE_ADV_NONCONN_IND;
break;
case BLE_GAP_OP_S_DIR:
hap.adv_type = BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD;
memcpy(hap.peer_addr, ble_gap_slave.dir_addr,
sizeof hap.peer_addr);
break;
case BLE_GAP_OP_S_UND:
hap.adv_type = BLE_HCI_ADV_TYPE_ADV_IND;
break;
default:
assert(0);
break;
}
ble_hci_sched_set_ack_cb(ble_gap_adv_ack, NULL);
rc = host_hci_cmd_le_set_adv_params(&hap);
if (rc != 0) {
ble_gap_call_slave_cb(BLE_GAP_EVENT_ADV_FAILURE,
BLE_HS_HCI_ERR(rc), 1);
return 1;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_adv_initiate(void)
{
int rc;
rc = ble_hci_sched_enqueue(ble_gap_adv_params_tx, NULL,
&ble_gap_slave.hci_handle);
if (rc != 0) {
ble_gap_slave_reset_state();
return rc;
}
return 0;
}
/**
* Enables the specified discoverable mode and connectable mode, and initiates
* the advertising process.
*
* Lock restrictions:
* o Caller unlocks gap.
*
* @param discoverable_mode One of the following constants:
* o BLE_GAP_DISC_MODE_NON
* (non-discoverable; 3.C.9.2.2).
* o BLE_GAP_DISC_MODE_LTD
* (limited-discoverable; 3.C.9.2.3).
* o BLE_GAP_DISC_MODE_GEN
* (general-discoverable; 3.C.9.2.4).
* @param connectable_mode One of the following constants:
* o BLE_GAP_CONN_MODE_NON
* (non-connectable; 3.C.9.3.2).
* o BLE_GAP_CONN_MODE_DIR
* (directed-connectable; 3.C.9.3.3).
* o BLE_GAP_CONN_MODE_UND
* (undirected-connectable; 3.C.9.3.4).
* @param peer_addr The address of the peer who is allowed to
* connect; only meaningful for directed
* connectable mode. For other modes, specify
* NULL.
* @param peer_addr_type The type of address specified for the
* "peer_addr" parameter; only meaningful for
* directed connectable mode. For other
* modes, specify 0. For directed connectable
* mode, this should be one of the following
* constants:
* o BLE_HCI_ADV_PEER_ADDR_PUBLIC
* o BLE_HCI_ADV_PEER_ADDR_RANDOM
*
* @return 0 on success; nonzero on failure.
*/
int
ble_gap_adv_start(uint8_t discoverable_mode, uint8_t connectable_mode,
uint8_t *peer_addr, uint8_t peer_addr_type,
struct hci_adv_params *adv_params,
ble_gap_conn_fn *cb, void *cb_arg)
{
#if !NIMBLE_OPT_ADVERTISE
return BLE_HS_ENOTSUP;
#endif
uint8_t op;
int rc;
if (discoverable_mode >= BLE_GAP_DISC_MODE_MAX ||
connectable_mode >= BLE_GAP_CONN_MODE_MAX) {
return BLE_HS_EINVAL;
}
/* Don't initiate a connection procedure if we won't be able to allocate a
* connection object on completion.
*/
if (connectable_mode != BLE_GAP_CONN_MODE_NON &&
!ble_hs_conn_can_alloc()) {
return BLE_HS_ENOMEM;
}
switch (connectable_mode) {
case BLE_GAP_CONN_MODE_NON:
op = BLE_GAP_OP_S_NON;
break;
case BLE_GAP_CONN_MODE_UND:
op = BLE_GAP_OP_S_UND;
break;
case BLE_GAP_CONN_MODE_DIR:
if (peer_addr_type != BLE_ADDR_TYPE_PUBLIC &&
peer_addr_type != BLE_ADDR_TYPE_RANDOM) {
return BLE_HS_EINVAL;
}
op = BLE_GAP_OP_S_DIR;
break;
default:
assert(0);
break;
}
rc = ble_gap_slave_set_op(op);
if (rc != 0) {
return rc;
}
if (connectable_mode == BLE_GAP_CONN_MODE_DIR) {
ble_gap_slave.dir_addr_type = peer_addr_type;
memcpy(ble_gap_slave.dir_addr, peer_addr, 6);
}
ble_gap_slave.cb = cb;
ble_gap_slave.cb_arg = cb_arg;
ble_gap_slave.state = 0;
ble_gap_slave.disc_mode = discoverable_mode;
if (adv_params != NULL) {
ble_gap_slave.adv_params = *adv_params;
} else {
ble_gap_slave.adv_params = ble_gap_adv_params_dflt;
}
ble_gap_adv_itvls(discoverable_mode, connectable_mode,
&ble_gap_slave.adv_params.adv_itvl_min,
&ble_gap_slave.adv_params.adv_itvl_max);
rc = ble_gap_adv_initiate();
if (rc != 0) {
return rc;
}
return 0;
}
/**
* Lock restrictions: None.
*/
int
ble_gap_adv_set_fields(struct ble_hs_adv_fields *adv_fields)
{
#if !NIMBLE_OPT_ADVERTISE
return BLE_HS_ENOTSUP;
#endif
int rc;
rc = ble_hs_adv_set_fields(adv_fields, ble_gap_slave.adv_data,
&ble_gap_slave.adv_data_len,
BLE_GAP_ADV_DATA_LIMIT);
if (rc != 0) {
return rc;
}
return 0;
}
/*****************************************************************************
* $discovery procedures *
*****************************************************************************/
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_disc_ack_disable(struct ble_hci_ack *ack, void *arg)
{
assert(ble_gap_master.op == BLE_GAP_OP_M_DISC);
assert(ble_gap_master.state == BLE_GAP_STATE_M_DISC_DISABLE);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
ble_gap_master_failed(ack->bha_status);
} else {
ble_gap_master_failed(0);
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_disc_tx_disable(void *arg)
{
int rc;
assert(ble_gap_master.op == BLE_GAP_OP_M_DISC);
assert(ble_gap_master.state == BLE_GAP_STATE_M_DISC_DISABLE);
ble_hci_sched_set_ack_cb(ble_gap_disc_ack_disable, NULL);
rc = host_hci_cmd_le_set_scan_enable(0, 0);
if (rc != 0) {
/* XXX: What can we do? */
ble_gap_master_failed(rc);
return rc;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_disc_ack_enable(struct ble_hci_ack *ack, void *arg)
{
assert(ble_gap_master.op == BLE_GAP_OP_M_DISC);
assert(ble_gap_master.state == BLE_GAP_STATE_M_DISC_ENABLE);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
ble_gap_master_failed(ack->bha_status);
} else {
ble_gap_master.state = BLE_GAP_STATE_M_DISC_ACKED;
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_disc_tx_enable(void *arg)
{
int rc;
assert(ble_gap_master.op == BLE_GAP_OP_M_DISC);
assert(ble_gap_master.state == BLE_GAP_STATE_M_DISC_ENABLE);
ble_hci_sched_set_ack_cb(ble_gap_disc_ack_enable, NULL);
rc = host_hci_cmd_le_set_scan_enable(1, 0);
if (rc != 0) {
ble_gap_master_failed(rc);
return rc;
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_disc_ack_params(struct ble_hci_ack *ack, void *arg)
{
assert(ble_gap_master.op == BLE_GAP_OP_M_DISC);
assert(ble_gap_master.state == BLE_GAP_STATE_M_DISC_PARAMS);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
ble_gap_master_failed(ack->bha_status);
return;
}
ble_gap_master_enqueue(BLE_GAP_STATE_M_DISC_ENABLE, 1,
ble_gap_disc_tx_enable, NULL);
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_disc_tx_params(void *arg)
{
int rc;
assert(ble_gap_master.op == BLE_GAP_OP_M_DISC);
assert(ble_gap_master.state == BLE_GAP_STATE_M_DISC_PARAMS);
ble_hci_sched_set_ack_cb(ble_gap_disc_ack_params, NULL);
rc = host_hci_cmd_le_set_scan_params(
ble_gap_master.disc.scan_type,
BLE_GAP_SCAN_FAST_INTERVAL_MIN,
BLE_GAP_SCAN_FAST_WINDOW,
BLE_HCI_ADV_OWN_ADDR_PUBLIC,
ble_gap_master.disc.filter_policy);
if (rc != 0) {
ble_gap_master_failed(rc);
return rc;
}
return 0;
}
/**
* Performs the Limited or General Discovery Procedures, as described in
* vol. 3, part C, section 9.2.5 / 9.2.6.
*
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*
* @return 0 on success; nonzero on failure.
*/
int
ble_gap_disc(uint32_t duration_ms, uint8_t discovery_mode,
uint8_t scan_type, uint8_t filter_policy,
ble_gap_disc_fn *cb, void *cb_arg)
{
#if !NIMBLE_OPT_ROLE_OBSERVER
return BLE_HS_ENOTSUP;
#endif
int rc;
if (discovery_mode != BLE_GAP_DISC_MODE_LTD &&
discovery_mode != BLE_GAP_DISC_MODE_GEN) {
return BLE_HS_EINVAL;
}
if (scan_type != BLE_HCI_SCAN_TYPE_PASSIVE &&
scan_type != BLE_HCI_SCAN_TYPE_ACTIVE) {
return BLE_HS_EINVAL;
}
if (filter_policy > BLE_HCI_SCAN_FILT_MAX) {
return BLE_HS_EINVAL;
}
if (duration_ms == 0) {
duration_ms = BLE_GAP_GEN_DISC_SCAN_MIN;
}
rc = ble_gap_master_set_op(BLE_GAP_OP_M_DISC);
if (rc != 0) {
return rc;
}
ble_gap_master.disc.disc_mode = discovery_mode;
ble_gap_master.disc.scan_type = scan_type;
ble_gap_master.disc.filter_policy = filter_policy;
ble_gap_master.disc.cb = cb;
ble_gap_master.disc.cb_arg = cb_arg;
rc = ble_gap_master_enqueue(BLE_GAP_STATE_M_DISC_PARAMS, 0,
ble_gap_disc_tx_params, NULL);
if (rc != 0) {
return rc;
}
os_callout_reset(&ble_gap_master_timer.cf_c,
duration_ms * OS_TICKS_PER_SEC / 1000);
return 0;
}
/*****************************************************************************
* $connection establishment procedures *
*****************************************************************************/
/**
* Processes an HCI acknowledgement (either command status or command complete)
* while a master connection is being established.
*
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_conn_create_ack(struct ble_hci_ack *ack, void *arg)
{
assert(ble_gap_master.op == BLE_GAP_OP_M_CONN);
assert(ble_gap_master.state == BLE_GAP_STATE_M_UNACKED);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
ble_gap_master_failed(ack->bha_status);
return;
}
ble_gap_master.state = BLE_GAP_STATE_M_ACKED;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_conn_create_tx(void *arg)
{
struct hci_create_conn hcc;
int rc;
assert(ble_gap_master.op == BLE_GAP_OP_M_CONN);
assert(ble_gap_master.state == BLE_GAP_STATE_M_PENDING);
hcc.scan_itvl = ble_gap_master.conn.params.scan_itvl;
hcc.scan_window = ble_gap_master.conn.params.scan_window;
if (ble_gap_master.conn.addr_type == BLE_GAP_ADDR_TYPE_WL) {
hcc.filter_policy = BLE_HCI_CONN_FILT_USE_WL;
hcc.peer_addr_type = BLE_HCI_ADV_PEER_ADDR_PUBLIC;
memset(hcc.peer_addr, 0, sizeof hcc.peer_addr);
} else {
hcc.filter_policy = BLE_HCI_CONN_FILT_NO_WL;
hcc.peer_addr_type = ble_gap_master.conn.addr_type;
memcpy(hcc.peer_addr, ble_gap_master.conn.addr,
sizeof hcc.peer_addr);
}
hcc.own_addr_type = BLE_HCI_ADV_OWN_ADDR_PUBLIC;
hcc.conn_itvl_min = ble_gap_master.conn.params.itvl_min;
hcc.conn_itvl_max = ble_gap_master.conn.params.itvl_max;
hcc.conn_latency = ble_gap_master.conn.params.latency;
hcc.supervision_timeout =
ble_gap_master.conn.params.supervision_timeout;
hcc.min_ce_len = ble_gap_master.conn.params.min_ce_len;
hcc.max_ce_len = ble_gap_master.conn.params.max_ce_len;
ble_gap_master.state = BLE_GAP_STATE_M_UNACKED;
ble_hci_sched_set_ack_cb(ble_gap_conn_create_ack, NULL);
rc = host_hci_cmd_le_create_connection(&hcc);
if (rc != 0) {
ble_gap_master_failed(rc);
return 1;
}
return 0;
}
/**
* Performs the Direct Connection Establishment Procedure, as described in
* vol. 3, part C, section 9.3.8.
*
* @param addr_type The peer's address type; one of:
* o BLE_HCI_CONN_PEER_ADDR_PUBLIC
* o BLE_HCI_CONN_PEER_ADDR_RANDOM
* o BLE_HCI_CONN_PEER_ADDR_PUBLIC_IDENT
* o BLE_HCI_CONN_PEER_ADDR_RANDOM_IDENT
* o BLE_GAP_ADDR_TYPE_WL
* @param addr The address of the peer to connect to.
*
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*
* @return 0 on success; nonzero on failure.
*/
int
ble_gap_conn_initiate(int addr_type, uint8_t *addr,
struct ble_gap_crt_params *params,
ble_gap_conn_fn *cb, void *cb_arg)
{
#if !NIMBLE_OPT_ROLE_CENTRAL
return BLE_HS_ENOTSUP;
#endif
int rc;
if (addr_type != BLE_ADDR_TYPE_PUBLIC &&
addr_type != BLE_ADDR_TYPE_RANDOM &&
addr_type != BLE_GAP_ADDR_TYPE_WL) {
return BLE_HS_EINVAL;
}
rc = ble_gap_master_set_op(BLE_GAP_OP_M_CONN);
if (rc != 0) {
return rc;
}
if (params == NULL) {
ble_gap_master.conn.params = ble_gap_params_dflt;
} else {
/* XXX: Verify params. */
ble_gap_master.conn.params = *params;
}
ble_gap_master.conn.addr_type = addr_type;
ble_gap_master.conn.cb = cb;
ble_gap_master.conn.cb_arg = cb_arg;
if (addr_type != BLE_GAP_ADDR_TYPE_WL) {
memcpy(ble_gap_master.conn.addr, addr, BLE_DEV_ADDR_LEN);
}
rc = ble_gap_master_enqueue(BLE_GAP_STATE_M_PENDING, 0,
ble_gap_conn_create_tx, NULL);
if (rc != 0) {
return rc;
}
return 0;
}
/*****************************************************************************
* $terminate connection procedure *
*****************************************************************************/
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_terminate_ack(struct ble_hci_ack *ack, void *arg)
{
struct ble_gap_snapshot snap;
uint16_t handle;
int rc;
if (ack->bha_status != 0) {
handle = (uintptr_t)arg;
rc = ble_gap_find_snapshot(handle, &snap);
if (rc == 0) {
ble_gap_call_conn_cb(BLE_GAP_EVENT_TERM_FAILURE, ack->bha_status,
&snap, NULL, NULL);
}
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_terminate_tx(void *arg)
{
struct ble_gap_snapshot snap;
uint16_t handle;
int status;
int rc;
handle = (uintptr_t)arg;
ble_hci_sched_set_ack_cb(ble_gap_terminate_ack, arg);
status = host_hci_cmd_disconnect(handle, BLE_ERR_REM_USER_CONN_TERM);
if (status != 0) {
rc = ble_gap_find_snapshot(handle, &snap);
if (rc == 0) {
ble_gap_call_conn_cb(BLE_GAP_EVENT_TERM_FAILURE, status, &snap,
NULL, NULL);
}
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
int
ble_gap_terminate(uint16_t conn_handle)
{
int rc;
if (!ble_hs_conn_exists(conn_handle)) {
return BLE_HS_ENOENT;
}
rc = ble_hci_sched_enqueue(ble_gap_terminate_tx,
(void *)(uintptr_t)conn_handle, NULL);
if (rc != 0) {
return rc;
}
return 0;
}
/*****************************************************************************
* $cancel *
*****************************************************************************/
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_cancel_ack(struct ble_hci_ack *ack, void *arg)
{
if (ack->bha_status != 0) {
ble_gap_call_master_conn_cb(BLE_GAP_EVENT_CANCEL_FAILURE,
ack->bha_status, 0);
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_cancel_tx(void *arg)
{
int rc;
ble_hci_sched_set_ack_cb(ble_gap_cancel_ack, NULL);
rc = host_hci_cmd_le_create_conn_cancel();
if (rc != 0) {
ble_gap_call_master_conn_cb(BLE_GAP_EVENT_CANCEL_FAILURE, rc, 0);
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
int
ble_gap_cancel(void)
{
int rc;
ble_gap_lock();
if (!ble_gap_master_in_progress()) {
rc = BLE_HS_ENOENT;
} else {
if (ble_gap_master.hci_handle != BLE_HCI_SCHED_HANDLE_NONE) {
ble_hci_sched_cancel(ble_gap_master.hci_handle);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
}
rc = ble_hci_sched_enqueue(ble_gap_cancel_tx, NULL, NULL);
}
ble_gap_unlock();
return rc;
}
/*****************************************************************************
* $update connection parameters *
*****************************************************************************/
/**
* Lock restrictions:
* o Caller unlocks gap.
*/
static void
ble_gap_param_neg_reply_ack(struct ble_hci_ack *ack, void *arg)
{
struct ble_gap_update_entry *entry;
ble_gap_lock();
entry = arg;
assert(entry->state == BLE_GAP_STATE_U_NEG_REPLY);
SLIST_REMOVE(&ble_gap_update_entries, entry,
ble_gap_update_entry, next);
ble_gap_unlock();
ble_gap_update_entry_free(entry);
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_param_reply_ack(struct ble_hci_ack *ack, void *arg)
{
struct ble_gap_update_entry *entry;
ble_gap_lock();
entry = arg;
assert(entry->state == BLE_GAP_STATE_U_REPLY);
entry->hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
SLIST_REMOVE(&ble_gap_update_entries, entry,
ble_gap_update_entry, next);
} else {
entry->state = BLE_GAP_STATE_U_REPLY_ACKED;
}
ble_gap_unlock();
if (ack->bha_status != 0) {
ble_gap_update_failed(entry, ack->bha_status);
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
void
ble_gap_rx_param_req(struct hci_le_conn_param_req *evt)
{
#if !NIMBLE_OPT_CONNECT
return;
#endif
struct hci_conn_param_neg_reply neg_reply;
struct ble_gap_upd_params peer_params;
struct ble_gap_update_entry *entry;
struct hci_conn_param_reply pos_reply;
struct ble_gap_snapshot snap;
int rc;
rc = ble_gap_find_snapshot(evt->connection_handle, &snap);
if (rc != 0) {
/* We are not connected to the sender. */
return;
}
ble_gap_lock();
entry = ble_gap_update_find(evt->connection_handle);
if (entry != NULL) {
/* Parameter update already in progress; replace existing request with
* new one.
*/
ble_gap_update_entry_remove_free(entry);
}
ble_gap_unlock();
peer_params.itvl_min = evt->itvl_min;
peer_params.itvl_max = evt->itvl_max;
peer_params.latency = evt->latency;
peer_params.supervision_timeout = evt->timeout;
peer_params.min_ce_len = 0;
peer_params.max_ce_len = 0;
entry = ble_gap_update_entry_alloc(evt->connection_handle,
&peer_params,
BLE_GAP_STATE_U_REPLY);
if (entry == NULL) {
/* Out of memory; reject. */
rc = BLE_ERR_MEM_CAPACITY;
} else {
rc = ble_gap_call_conn_cb(BLE_GAP_EVENT_CONN_UPDATE_REQ, 0, &snap,
&entry->params, &peer_params);
}
if (rc == 0) {
pos_reply.handle = entry->conn_handle;
pos_reply.conn_itvl_min = entry->params.itvl_min;
pos_reply.conn_itvl_max = entry->params.itvl_max;
pos_reply.conn_latency = entry->params.latency;
pos_reply.supervision_timeout = entry->params.supervision_timeout;
pos_reply.min_ce_len = entry->params.min_ce_len;
pos_reply.max_ce_len = entry->params.max_ce_len;
ble_hci_sched_set_ack_cb(ble_gap_param_reply_ack, entry);
rc = host_hci_cmd_le_conn_param_reply(&pos_reply);
}
if (rc != 0) {
neg_reply.handle = evt->connection_handle;
neg_reply.reason = rc;
if (entry != NULL) {
entry->state = BLE_GAP_STATE_U_NEG_REPLY;
}
ble_hci_sched_set_ack_cb(ble_gap_param_neg_reply_ack, entry);
host_hci_cmd_le_conn_param_neg_reply(&neg_reply);
}
if (entry != NULL) {
ble_gap_lock();
SLIST_INSERT_HEAD(&ble_gap_update_entries, entry, next);
ble_gap_unlock();
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static void
ble_gap_update_ack(struct ble_hci_ack *ack, void *arg)
{
struct ble_gap_update_entry *entry;
ble_gap_lock();
entry = arg;
assert(entry->state == BLE_GAP_STATE_U_UPDATE);
ble_gap_master.hci_handle = BLE_HCI_SCHED_HANDLE_NONE;
if (ack->bha_status != 0) {
SLIST_REMOVE(&ble_gap_update_entries, entry,
ble_gap_update_entry, next);
} else {
entry->state = BLE_GAP_STATE_U_UPDATE_ACKED;
}
ble_gap_unlock();
if (ack->bha_status != 0) {
ble_gap_update_failed(entry, ack->bha_status);
}
}
/**
* Lock restrictions:
* o Caller unlocks all ble_hs mutexes.
*/
static int
ble_gap_update_tx(void *arg)
{
struct ble_gap_update_entry *entry;
struct hci_conn_update cmd;
int rc;
ble_gap_lock();
entry = arg;
assert(entry->state == BLE_GAP_STATE_U_UPDATE);
cmd.handle = entry->conn_handle;
cmd.conn_itvl_min = entry->params.itvl_min;
cmd.conn_itvl_max = entry->params.itvl_max;
cmd.conn_latency = entry->params.latency;
cmd.supervision_timeout = entry->params.supervision_timeout;
cmd.min_ce_len = entry->params.min_ce_len;
cmd.max_ce_len = entry->params.max_ce_len;
ble_hci_sched_set_ack_cb(ble_gap_update_ack, entry);
rc = host_hci_cmd_le_conn_update(&cmd);
if (rc != 0) {
SLIST_REMOVE(&ble_gap_update_entries, entry,
ble_gap_update_entry, next);
}
ble_gap_unlock();
if (rc != 0) {
ble_gap_update_failed(entry, rc);
}
return 0;
}
/**
* Lock restrictions:
* o Caller unlocks ble_hs_conn.
* o Caller unlocks gap.
*/
int
ble_gap_update_params(uint16_t conn_handle, struct ble_gap_upd_params *params)
{
#if !NIMBLE_OPT_CONNECT
return BLE_HS_ENOTSUP;
#endif
struct ble_gap_update_entry *entry;
int rc;
if (!ble_hs_conn_exists(conn_handle)) {
return BLE_HS_ENOTCONN;
}
ble_gap_lock();
entry = ble_gap_update_find(conn_handle);
ble_gap_unlock();
if (entry != NULL) {
return BLE_HS_EALREADY;
}
entry = ble_gap_update_entry_alloc(conn_handle, params,
BLE_GAP_STATE_U_UPDATE);
if (entry == NULL) {
return BLE_HS_ENOMEM;
}
entry->conn_handle = conn_handle;
entry->params = *params;
entry->state = BLE_GAP_STATE_U_UPDATE;
rc = ble_hci_sched_enqueue(ble_gap_update_tx, entry, &entry->hci_handle);
if (rc == 0) {
ble_gap_lock();
SLIST_INSERT_HEAD(&ble_gap_update_entries, entry, next);
ble_gap_unlock();
}
return rc;
}
/*****************************************************************************
* $init *
*****************************************************************************/
/**
* Lock restrictions: None.
*/
int
ble_gap_init(void)
{
int rc;
memset(&ble_gap_master, 0, sizeof ble_gap_master);
memset(&ble_gap_slave, 0, sizeof ble_gap_slave);
memset(&ble_gap_wl, 0, sizeof ble_gap_wl);
free(ble_gap_update_mem);
ble_gap_update_mem = malloc(
OS_MEMPOOL_BYTES(ble_hs_cfg.max_conn_update_entries,
sizeof (struct ble_gap_update_entry)));
os_callout_func_init(&ble_gap_master_timer, &ble_hs_evq,
ble_gap_master_timer_exp, NULL);
os_callout_func_init(&ble_gap_slave_timer, &ble_hs_evq,
ble_gap_slave_timer_exp, NULL);
if (ble_hs_cfg.max_conn_update_entries > 0) {
rc = os_mempool_init(&ble_gap_update_pool,
ble_hs_cfg.max_conn_update_entries,
sizeof (struct ble_gap_update_entry),
ble_gap_update_mem, "ble_gap_update_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
}
SLIST_INIT(&ble_gap_update_entries);
return 0;
err:
free(ble_gap_update_mem);
ble_gap_update_mem = NULL;
return rc;
}