mirror of
https://github.com/Fishwaldo/bl_mcu_sdk.git
synced 2025-07-12 15:58:38 +00:00
2739 lines
70 KiB
C
2739 lines
70 KiB
C
/* conn.c - Bluetooth connection handling */
|
|
|
|
/*
|
|
* Copyright (c) 2015-2016 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <atomic.h>
|
|
#include <misc/byteorder.h>
|
|
#include <misc/util.h>
|
|
#include <misc/slist.h>
|
|
#include <misc/stack.h>
|
|
#include <misc/__assert.h>
|
|
|
|
#include <hci_host.h>
|
|
#include <bluetooth.h>
|
|
#include <conn.h>
|
|
#include <hci_driver.h>
|
|
#include <att.h>
|
|
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_CONN)
|
|
#define LOG_MODULE_NAME bt_conn
|
|
#include "log.h"
|
|
|
|
#include "hci_core.h"
|
|
#include "conn_internal.h"
|
|
#include "l2cap_internal.h"
|
|
#include "keys.h"
|
|
#include "smp.h"
|
|
#include "att_internal.h"
|
|
#include "gatt_internal.h"
|
|
#if defined(BFLB_BLE)
|
|
#include "config.h"
|
|
|
|
extern struct k_sem g_poll_sem;
|
|
#endif
|
|
struct tx_meta {
|
|
struct bt_conn_tx *tx;
|
|
};
|
|
|
|
#define tx_data(buf) ((struct tx_meta *)net_buf_user_data(buf))
|
|
|
|
#if !defined(BFLB_DYNAMIC_ALLOC_MEM)
|
|
NET_BUF_POOL_DEFINE(acl_tx_pool, CONFIG_BT_L2CAP_TX_BUF_COUNT,
|
|
BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU),
|
|
sizeof(struct tx_meta), NULL);
|
|
#else
|
|
struct net_buf_pool acl_tx_pool;
|
|
#endif
|
|
|
|
#if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0
|
|
|
|
#if defined(BT_CTLR_TX_BUFFER_SIZE)
|
|
#define FRAG_SIZE BT_L2CAP_BUF_SIZE(BT_CTLR_TX_BUFFER_SIZE - 4)
|
|
#else
|
|
#define FRAG_SIZE BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU)
|
|
#endif
|
|
|
|
#if !defined(BFLB_DYNAMIC_ALLOC_MEM)
|
|
/* Dedicated pool for fragment buffers in case queued up TX buffers don't
|
|
* fit the controllers buffer size. We can't use the acl_tx_pool for the
|
|
* fragmentation, since it's possible that pool is empty and all buffers
|
|
* are queued up in the TX queue. In such a situation, trying to allocate
|
|
* another buffer from the acl_tx_pool would result in a deadlock.
|
|
*/
|
|
NET_BUF_POOL_FIXED_DEFINE(frag_pool, CONFIG_BT_L2CAP_TX_FRAG_COUNT, FRAG_SIZE,
|
|
NULL);
|
|
#else
|
|
struct net_buf_pool frag_pool;
|
|
#endif
|
|
#endif /* CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0 */
|
|
|
|
/* How long until we cancel HCI_LE_Create_Connection */
|
|
#define CONN_TIMEOUT K_SECONDS(CONFIG_BT_CREATE_CONN_TIMEOUT)
|
|
|
|
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
|
|
const struct bt_conn_auth_cb *bt_auth;
|
|
#endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR */
|
|
|
|
static struct bt_conn conns[CONFIG_BT_MAX_CONN];
|
|
static struct bt_conn_cb *callback_list;
|
|
|
|
static struct bt_conn_tx conn_tx[CONFIG_BT_CONN_TX_MAX];
|
|
K_FIFO_DEFINE(free_tx);
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
static struct bt_conn sco_conns[CONFIG_BT_MAX_SCO_CONN];
|
|
|
|
enum pairing_method {
|
|
LEGACY, /* Legacy (pre-SSP) pairing */
|
|
JUST_WORKS, /* JustWorks pairing */
|
|
PASSKEY_INPUT, /* Passkey Entry input */
|
|
PASSKEY_DISPLAY, /* Passkey Entry display */
|
|
PASSKEY_CONFIRM, /* Passkey confirm */
|
|
};
|
|
|
|
/* based on table 5.7, Core Spec 4.2, Vol.3 Part C, 5.2.2.6 */
|
|
static const u8_t ssp_method[4 /* remote */][4 /* local */] = {
|
|
{ JUST_WORKS, JUST_WORKS, PASSKEY_INPUT, JUST_WORKS },
|
|
{ JUST_WORKS, PASSKEY_CONFIRM, PASSKEY_INPUT, JUST_WORKS },
|
|
{ PASSKEY_DISPLAY, PASSKEY_DISPLAY, PASSKEY_INPUT, JUST_WORKS },
|
|
{ JUST_WORKS, JUST_WORKS, JUST_WORKS, JUST_WORKS },
|
|
};
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
struct k_sem *bt_conn_get_pkts(struct bt_conn *conn)
|
|
{
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (conn->type == BT_CONN_TYPE_BR || !bt_dev.le.mtu) {
|
|
return &bt_dev.br.pkts;
|
|
}
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
return &bt_dev.le.pkts;
|
|
}
|
|
|
|
static inline const char *state2str(bt_conn_state_t state)
|
|
{
|
|
switch (state) {
|
|
case BT_CONN_DISCONNECTED:
|
|
return "disconnected";
|
|
case BT_CONN_CONNECT_SCAN:
|
|
return "connect-scan";
|
|
case BT_CONN_CONNECT_DIR_ADV:
|
|
return "connect-dir-adv";
|
|
case BT_CONN_CONNECT:
|
|
return "connect";
|
|
case BT_CONN_CONNECTED:
|
|
return "connected";
|
|
case BT_CONN_DISCONNECT:
|
|
return "disconnect";
|
|
default:
|
|
return "(unknown)";
|
|
}
|
|
}
|
|
|
|
static void notify_connected(struct bt_conn *conn)
|
|
{
|
|
struct bt_conn_cb *cb;
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (conn->type == BT_CONN_TYPE_BR && conn->err) {
|
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) {
|
|
atomic_clear_bit(bt_dev.flags, BT_DEV_ISCAN);
|
|
}
|
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) {
|
|
atomic_clear_bit(bt_dev.flags, BT_DEV_PSCAN);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (cb = callback_list; cb; cb = cb->_next) {
|
|
if (cb->connected) {
|
|
cb->connected(conn, conn->err);
|
|
}
|
|
}
|
|
|
|
if (conn->type == BT_CONN_TYPE_LE && !conn->err) {
|
|
bt_gatt_connected(conn);
|
|
}
|
|
}
|
|
|
|
void notify_disconnected(struct bt_conn *conn)
|
|
{
|
|
struct bt_conn_cb *cb;
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (conn->type == BT_CONN_TYPE_BR) {
|
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) {
|
|
atomic_clear_bit(bt_dev.flags, BT_DEV_ISCAN);
|
|
}
|
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) {
|
|
atomic_clear_bit(bt_dev.flags, BT_DEV_PSCAN);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (cb = callback_list; cb; cb = cb->_next) {
|
|
if (cb->disconnected) {
|
|
cb->disconnected(conn, conn->err);
|
|
}
|
|
}
|
|
}
|
|
|
|
void notify_le_param_updated(struct bt_conn *conn)
|
|
{
|
|
struct bt_conn_cb *cb;
|
|
|
|
/* If new connection parameters meet requirement of pending
|
|
* parameters don't send slave conn param request anymore on timeout
|
|
*/
|
|
if (atomic_test_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET) &&
|
|
conn->le.interval >= conn->le.interval_min &&
|
|
conn->le.interval <= conn->le.interval_max &&
|
|
conn->le.latency == conn->le.pending_latency &&
|
|
conn->le.timeout == conn->le.pending_timeout) {
|
|
atomic_clear_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET);
|
|
}
|
|
|
|
for (cb = callback_list; cb; cb = cb->_next) {
|
|
if (cb->le_param_updated) {
|
|
cb->le_param_updated(conn, conn->le.interval,
|
|
conn->le.latency,
|
|
conn->le.timeout);
|
|
}
|
|
}
|
|
}
|
|
|
|
void notify_le_phy_updated(struct bt_conn *conn, u8_t tx_phy, u8_t rx_phy)
|
|
{
|
|
struct bt_conn_cb *cb;
|
|
|
|
for (cb = callback_list; cb; cb = cb->_next) {
|
|
if (cb->le_phy_updated) {
|
|
cb->le_phy_updated(conn, tx_phy, rx_phy);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
|
|
{
|
|
struct bt_conn_cb *cb;
|
|
|
|
if (!bt_le_conn_params_valid(param)) {
|
|
return false;
|
|
}
|
|
|
|
for (cb = callback_list; cb; cb = cb->_next) {
|
|
if (!cb->le_param_req) {
|
|
continue;
|
|
}
|
|
|
|
if (!cb->le_param_req(conn, param)) {
|
|
return false;
|
|
}
|
|
|
|
/* The callback may modify the parameters so we need to
|
|
* double-check that it returned valid parameters.
|
|
*/
|
|
if (!bt_le_conn_params_valid(param)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Default to accepting if there's no app callback */
|
|
return true;
|
|
}
|
|
|
|
static int send_conn_le_param_update(struct bt_conn *conn,
|
|
const struct bt_le_conn_param *param)
|
|
{
|
|
BT_DBG("conn %p features 0x%02x params (%d-%d %d %d)", conn,
|
|
conn->le.features[0], param->interval_min,
|
|
param->interval_max, param->latency, param->timeout);
|
|
|
|
/* Use LE connection parameter request if both local and remote support
|
|
* it; or if local role is master then use LE connection update.
|
|
*/
|
|
if ((BT_FEAT_LE_CONN_PARAM_REQ_PROC(bt_dev.le.features) &&
|
|
BT_FEAT_LE_CONN_PARAM_REQ_PROC(conn->le.features) &&
|
|
!atomic_test_bit(conn->flags, BT_CONN_SLAVE_PARAM_L2CAP)) ||
|
|
(conn->role == BT_HCI_ROLE_MASTER)) {
|
|
int rc;
|
|
|
|
rc = bt_conn_le_conn_update(conn, param);
|
|
|
|
/* store those in case of fallback to L2CAP */
|
|
if (rc == 0) {
|
|
conn->le.pending_latency = param->latency;
|
|
conn->le.pending_timeout = param->timeout;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* If remote master does not support LL Connection Parameters Request
|
|
* Procedure
|
|
*/
|
|
return bt_l2cap_update_conn_param(conn, param);
|
|
}
|
|
|
|
static void tx_free(struct bt_conn_tx *tx)
|
|
{
|
|
tx->cb = NULL;
|
|
tx->user_data = NULL;
|
|
tx->pending_no_cb = 0U;
|
|
k_fifo_put(&free_tx, tx);
|
|
}
|
|
|
|
static void tx_notify(struct bt_conn *conn)
|
|
{
|
|
BT_DBG("conn %p", conn);
|
|
|
|
while (1) {
|
|
struct bt_conn_tx *tx;
|
|
unsigned int key;
|
|
bt_conn_tx_cb_t cb;
|
|
void *user_data;
|
|
|
|
key = irq_lock();
|
|
if (sys_slist_is_empty(&conn->tx_complete)) {
|
|
irq_unlock(key);
|
|
break;
|
|
}
|
|
|
|
tx = (void *)sys_slist_get_not_empty(&conn->tx_complete);
|
|
irq_unlock(key);
|
|
|
|
BT_DBG("tx %p cb %p user_data %p", tx, tx->cb, tx->user_data);
|
|
|
|
/* Copy over the params */
|
|
cb = tx->cb;
|
|
user_data = tx->user_data;
|
|
|
|
/* Free up TX notify since there may be user waiting */
|
|
tx_free(tx);
|
|
|
|
/* Run the callback, at this point it should be safe to
|
|
* allocate new buffers since the TX should have been
|
|
* unblocked by tx_free.
|
|
*/
|
|
cb(conn, user_data);
|
|
}
|
|
}
|
|
|
|
static void tx_complete_work(struct k_work *work)
|
|
{
|
|
struct bt_conn *conn = CONTAINER_OF(work, struct bt_conn,
|
|
tx_complete_work);
|
|
|
|
BT_DBG("conn %p", conn);
|
|
|
|
tx_notify(conn);
|
|
}
|
|
|
|
static void conn_update_timeout(struct k_work *work)
|
|
{
|
|
struct bt_conn *conn = CONTAINER_OF(work, struct bt_conn, update_work);
|
|
const struct bt_le_conn_param *param;
|
|
|
|
BT_DBG("conn %p", conn);
|
|
|
|
if (conn->state == BT_CONN_DISCONNECTED) {
|
|
bt_l2cap_disconnected(conn);
|
|
#if !defined(BFLB_BLE)
|
|
notify_disconnected(conn);
|
|
#endif
|
|
|
|
/* Release the reference we took for the very first
|
|
* state transition.
|
|
*/
|
|
bt_conn_unref(conn);
|
|
return;
|
|
}
|
|
|
|
if (conn->type != BT_CONN_TYPE_LE) {
|
|
return;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
|
|
conn->role == BT_CONN_ROLE_MASTER) {
|
|
/* we don't call bt_conn_disconnect as it would also clear
|
|
* auto connect flag if it was set, instead just cancel
|
|
* connection directly
|
|
*/
|
|
bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CONN_CANCEL, NULL, NULL);
|
|
return;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS)
|
|
/* if application set own params use those, otherwise use defaults */
|
|
if (atomic_test_and_clear_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET)) {
|
|
param = BT_LE_CONN_PARAM(conn->le.interval_min,
|
|
conn->le.interval_max,
|
|
conn->le.pending_latency,
|
|
conn->le.pending_timeout);
|
|
|
|
send_conn_le_param_update(conn, param);
|
|
} else {
|
|
param = BT_LE_CONN_PARAM(CONFIG_BT_PERIPHERAL_PREF_MIN_INT,
|
|
CONFIG_BT_PERIPHERAL_PREF_MAX_INT,
|
|
CONFIG_BT_PERIPHERAL_PREF_SLAVE_LATENCY,
|
|
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT);
|
|
|
|
send_conn_le_param_update(conn, param);
|
|
}
|
|
#else
|
|
/* update only if application set own params */
|
|
if (atomic_test_and_clear_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET)) {
|
|
param = BT_LE_CONN_PARAM(conn->le.interval_min,
|
|
conn->le.interval_max,
|
|
conn->le.latency,
|
|
conn->le.timeout);
|
|
|
|
send_conn_le_param_update(conn, param);
|
|
}
|
|
#endif
|
|
|
|
atomic_set_bit(conn->flags, BT_CONN_SLAVE_PARAM_UPDATE);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_AUDIO)
|
|
struct bt_conn *iso_conn_new(struct bt_conn *conns, size_t size)
|
|
{
|
|
struct bt_conn *conn = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (atomic_cas(&conns[i].ref, 0, 1)) {
|
|
conn = &conns[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!conn) {
|
|
return NULL;
|
|
}
|
|
|
|
(void)memset(conn, 0, offsetof(struct bt_conn, ref));
|
|
|
|
return conn;
|
|
}
|
|
#endif
|
|
|
|
static struct bt_conn *conn_new(void)
|
|
{
|
|
struct bt_conn *conn = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
if (!atomic_get(&conns[i].ref)) {
|
|
conn = &conns[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!conn) {
|
|
return NULL;
|
|
}
|
|
|
|
(void)memset(conn, 0, sizeof(*conn));
|
|
k_delayed_work_init(&conn->update_work, conn_update_timeout);
|
|
|
|
k_work_init(&conn->tx_complete_work, tx_complete_work);
|
|
|
|
atomic_set(&conn->ref, 1);
|
|
|
|
return conn;
|
|
}
|
|
|
|
#if defined(BFLB_BLE)
|
|
bool le_check_valid_conn(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
if (atomic_get(&conns[i].ref)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if defined(BFLB_HOST_ASSISTANT)
|
|
void bt_notify_disconnected(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
if (atomic_get(&conns[i].ref)) {
|
|
conns[i].err = BT_HCI_ERR_UNSPECIFIED;
|
|
notify_disconnected(&conns[i]);
|
|
}
|
|
}
|
|
}
|
|
#endif //#if defined(BFLB_HOST_ASSISTANT)
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
void bt_sco_cleanup(struct bt_conn *sco_conn)
|
|
{
|
|
bt_conn_unref(sco_conn->sco.acl);
|
|
sco_conn->sco.acl = NULL;
|
|
bt_conn_unref(sco_conn);
|
|
}
|
|
|
|
static struct bt_conn *sco_conn_new(void)
|
|
{
|
|
struct bt_conn *sco_conn = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sco_conns); i++) {
|
|
if (!atomic_get(&sco_conns[i].ref)) {
|
|
sco_conn = &sco_conns[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!sco_conn) {
|
|
return NULL;
|
|
}
|
|
|
|
(void)memset(sco_conn, 0, sizeof(*sco_conn));
|
|
|
|
atomic_set(&sco_conn->ref, 1);
|
|
|
|
return sco_conn;
|
|
}
|
|
|
|
struct bt_conn *bt_conn_create_br(const bt_addr_t *peer,
|
|
const struct bt_br_conn_param *param)
|
|
{
|
|
struct bt_hci_cp_connect *cp;
|
|
struct bt_conn *conn;
|
|
struct net_buf *buf;
|
|
|
|
conn = bt_conn_lookup_addr_br(peer);
|
|
if (conn) {
|
|
switch (conn->state) {
|
|
case BT_CONN_CONNECT:
|
|
case BT_CONN_CONNECTED:
|
|
return conn;
|
|
default:
|
|
bt_conn_unref(conn);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
conn = bt_conn_add_br(peer);
|
|
if (!conn) {
|
|
return NULL;
|
|
}
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_CONNECT, sizeof(*cp));
|
|
if (!buf) {
|
|
bt_conn_unref(conn);
|
|
return NULL;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
|
|
(void)memset(cp, 0, sizeof(*cp));
|
|
|
|
memcpy(&cp->bdaddr, peer, sizeof(cp->bdaddr));
|
|
cp->packet_type = sys_cpu_to_le16(0xcc18); /* DM1 DH1 DM3 DH5 DM5 DH5 */
|
|
cp->pscan_rep_mode = 0x02; /* R2 */
|
|
cp->allow_role_switch = param->allow_role_switch ? 0x01 : 0x00;
|
|
cp->clock_offset = 0x0000; /* TODO used cached clock offset */
|
|
|
|
if (bt_hci_cmd_send_sync(BT_HCI_OP_CONNECT, buf, NULL) < 0) {
|
|
bt_conn_unref(conn);
|
|
return NULL;
|
|
}
|
|
|
|
bt_conn_set_state(conn, BT_CONN_CONNECT);
|
|
conn->role = BT_CONN_ROLE_MASTER;
|
|
|
|
bt_conn_unref(conn);
|
|
return conn;
|
|
}
|
|
|
|
struct bt_conn *bt_conn_create_sco(const bt_addr_t *peer)
|
|
{
|
|
struct bt_hci_cp_setup_sync_conn *cp;
|
|
struct bt_conn *sco_conn;
|
|
struct net_buf *buf;
|
|
int link_type;
|
|
|
|
sco_conn = bt_conn_lookup_addr_sco(peer);
|
|
if (sco_conn) {
|
|
switch (sco_conn->state) {
|
|
case BT_CONN_CONNECT:
|
|
case BT_CONN_CONNECTED:
|
|
return sco_conn;
|
|
default:
|
|
bt_conn_unref(sco_conn);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) {
|
|
link_type = BT_HCI_ESCO;
|
|
} else {
|
|
link_type = BT_HCI_SCO;
|
|
}
|
|
|
|
sco_conn = bt_conn_add_sco(peer, link_type);
|
|
if (!sco_conn) {
|
|
return NULL;
|
|
}
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_SETUP_SYNC_CONN, sizeof(*cp));
|
|
if (!buf) {
|
|
bt_sco_cleanup(sco_conn);
|
|
return NULL;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
|
|
(void)memset(cp, 0, sizeof(*cp));
|
|
|
|
BT_ERR("handle : %x", sco_conn->sco.acl->handle);
|
|
|
|
cp->handle = sco_conn->sco.acl->handle;
|
|
cp->pkt_type = sco_conn->sco.pkt_type;
|
|
cp->tx_bandwidth = 0x00001f40;
|
|
cp->rx_bandwidth = 0x00001f40;
|
|
cp->max_latency = 0x0007;
|
|
cp->retrans_effort = 0x01;
|
|
cp->content_format = BT_VOICE_CVSD_16BIT;
|
|
|
|
if (bt_hci_cmd_send_sync(BT_HCI_OP_SETUP_SYNC_CONN, buf,
|
|
NULL) < 0) {
|
|
bt_sco_cleanup(sco_conn);
|
|
return NULL;
|
|
}
|
|
|
|
bt_conn_set_state(sco_conn, BT_CONN_CONNECT);
|
|
|
|
return sco_conn;
|
|
}
|
|
|
|
struct bt_conn *bt_conn_lookup_addr_sco(const bt_addr_t *peer)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sco_conns); i++) {
|
|
if (!atomic_get(&sco_conns[i].ref)) {
|
|
continue;
|
|
}
|
|
|
|
if (sco_conns[i].type != BT_CONN_TYPE_SCO) {
|
|
continue;
|
|
}
|
|
|
|
if (!bt_addr_cmp(peer, &sco_conns[i].sco.acl->br.dst)) {
|
|
return bt_conn_ref(&sco_conns[i]);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct bt_conn *bt_conn_lookup_addr_br(const bt_addr_t *peer)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
if (!atomic_get(&conns[i].ref)) {
|
|
continue;
|
|
}
|
|
|
|
if (conns[i].type != BT_CONN_TYPE_BR) {
|
|
continue;
|
|
}
|
|
|
|
if (!bt_addr_cmp(peer, &conns[i].br.dst)) {
|
|
return bt_conn_ref(&conns[i]);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct bt_conn *bt_conn_add_sco(const bt_addr_t *peer, int link_type)
|
|
{
|
|
struct bt_conn *sco_conn = sco_conn_new();
|
|
|
|
if (!sco_conn) {
|
|
return NULL;
|
|
}
|
|
|
|
sco_conn->sco.acl = bt_conn_lookup_addr_br(peer);
|
|
sco_conn->type = BT_CONN_TYPE_SCO;
|
|
|
|
if (link_type == BT_HCI_SCO) {
|
|
if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) {
|
|
sco_conn->sco.pkt_type = (bt_dev.br.esco_pkt_type &
|
|
ESCO_PKT_MASK);
|
|
} else {
|
|
sco_conn->sco.pkt_type = (bt_dev.br.esco_pkt_type &
|
|
SCO_PKT_MASK);
|
|
}
|
|
} else if (link_type == BT_HCI_ESCO) {
|
|
sco_conn->sco.pkt_type = (bt_dev.br.esco_pkt_type &
|
|
~EDR_ESCO_PKT_MASK);
|
|
}
|
|
|
|
return sco_conn;
|
|
}
|
|
|
|
struct bt_conn *bt_conn_add_br(const bt_addr_t *peer)
|
|
{
|
|
struct bt_conn *conn = conn_new();
|
|
|
|
if (!conn) {
|
|
return NULL;
|
|
}
|
|
|
|
bt_addr_copy(&conn->br.dst, peer);
|
|
conn->type = BT_CONN_TYPE_BR;
|
|
|
|
return conn;
|
|
}
|
|
|
|
static int pin_code_neg_reply(const bt_addr_t *bdaddr)
|
|
{
|
|
struct bt_hci_cp_pin_code_neg_reply *cp;
|
|
struct net_buf *buf;
|
|
|
|
BT_DBG("");
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_PIN_CODE_NEG_REPLY, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
bt_addr_copy(&cp->bdaddr, bdaddr);
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_PIN_CODE_NEG_REPLY, buf, NULL);
|
|
}
|
|
|
|
static int pin_code_reply(struct bt_conn *conn, const char *pin, u8_t len)
|
|
{
|
|
struct bt_hci_cp_pin_code_reply *cp;
|
|
struct net_buf *buf;
|
|
|
|
BT_DBG("");
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_PIN_CODE_REPLY, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
|
|
bt_addr_copy(&cp->bdaddr, &conn->br.dst);
|
|
cp->pin_len = len;
|
|
strncpy((char *)cp->pin_code, pin, sizeof(cp->pin_code));
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_PIN_CODE_REPLY, buf, NULL);
|
|
}
|
|
|
|
int bt_conn_auth_pincode_entry(struct bt_conn *conn, const char *pin)
|
|
{
|
|
size_t len;
|
|
|
|
if (!bt_auth) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (conn->type != BT_CONN_TYPE_BR) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
len = strlen(pin);
|
|
if (len > 16) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (conn->required_sec_level == BT_SECURITY_L3 && len < 16) {
|
|
BT_WARN("PIN code for %s is not 16 bytes wide",
|
|
bt_addr_str(&conn->br.dst));
|
|
return -EPERM;
|
|
}
|
|
|
|
/* Allow user send entered PIN to remote, then reset user state. */
|
|
if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) {
|
|
return -EPERM;
|
|
}
|
|
|
|
if (len == 16) {
|
|
atomic_set_bit(conn->flags, BT_CONN_BR_LEGACY_SECURE);
|
|
}
|
|
|
|
return pin_code_reply(conn, pin, len);
|
|
}
|
|
|
|
void bt_conn_pin_code_req(struct bt_conn *conn)
|
|
{
|
|
if (bt_auth && bt_auth->pincode_entry) {
|
|
bool secure = false;
|
|
|
|
if (conn->required_sec_level == BT_SECURITY_L3) {
|
|
secure = true;
|
|
}
|
|
|
|
atomic_set_bit(conn->flags, BT_CONN_USER);
|
|
atomic_set_bit(conn->flags, BT_CONN_BR_PAIRING);
|
|
bt_auth->pincode_entry(conn, secure);
|
|
} else {
|
|
pin_code_neg_reply(&conn->br.dst);
|
|
}
|
|
}
|
|
|
|
u8_t bt_conn_get_io_capa(void)
|
|
{
|
|
if (!bt_auth) {
|
|
return BT_IO_NO_INPUT_OUTPUT;
|
|
}
|
|
|
|
if (bt_auth->passkey_confirm && bt_auth->passkey_display) {
|
|
return BT_IO_DISPLAY_YESNO;
|
|
}
|
|
|
|
if (bt_auth->passkey_entry) {
|
|
return BT_IO_KEYBOARD_ONLY;
|
|
}
|
|
|
|
if (bt_auth->passkey_display) {
|
|
return BT_IO_DISPLAY_ONLY;
|
|
}
|
|
|
|
return BT_IO_NO_INPUT_OUTPUT;
|
|
}
|
|
|
|
static u8_t ssp_pair_method(const struct bt_conn *conn)
|
|
{
|
|
return ssp_method[conn->br.remote_io_capa][bt_conn_get_io_capa()];
|
|
}
|
|
|
|
u8_t bt_conn_ssp_get_auth(const struct bt_conn *conn)
|
|
{
|
|
/* Validate no bond auth request, and if valid use it. */
|
|
if ((conn->br.remote_auth == BT_HCI_NO_BONDING) ||
|
|
((conn->br.remote_auth == BT_HCI_NO_BONDING_MITM) &&
|
|
(ssp_pair_method(conn) > JUST_WORKS))) {
|
|
return conn->br.remote_auth;
|
|
}
|
|
|
|
/* Local & remote have enough IO capabilities to get MITM protection. */
|
|
if (ssp_pair_method(conn) > JUST_WORKS) {
|
|
return conn->br.remote_auth | BT_MITM;
|
|
}
|
|
|
|
/* No MITM protection possible so ignore remote MITM requirement. */
|
|
return (conn->br.remote_auth & ~BT_MITM);
|
|
}
|
|
|
|
static int ssp_confirm_reply(struct bt_conn *conn)
|
|
{
|
|
struct bt_hci_cp_user_confirm_reply *cp;
|
|
struct net_buf *buf;
|
|
|
|
BT_DBG("");
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_USER_CONFIRM_REPLY, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
bt_addr_copy(&cp->bdaddr, &conn->br.dst);
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_USER_CONFIRM_REPLY, buf, NULL);
|
|
}
|
|
|
|
static int ssp_confirm_neg_reply(struct bt_conn *conn)
|
|
{
|
|
struct bt_hci_cp_user_confirm_reply *cp;
|
|
struct net_buf *buf;
|
|
|
|
BT_DBG("");
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_USER_CONFIRM_NEG_REPLY, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
bt_addr_copy(&cp->bdaddr, &conn->br.dst);
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_USER_CONFIRM_NEG_REPLY, buf,
|
|
NULL);
|
|
}
|
|
|
|
void bt_conn_ssp_auth_complete(struct bt_conn *conn, u8_t status)
|
|
{
|
|
if (!status) {
|
|
bool bond = !atomic_test_bit(conn->flags, BT_CONN_BR_NOBOND);
|
|
|
|
if (bt_auth && bt_auth->pairing_complete) {
|
|
bt_auth->pairing_complete(conn, bond);
|
|
}
|
|
} else {
|
|
if (bt_auth && bt_auth->pairing_failed) {
|
|
bt_auth->pairing_failed(conn, status);
|
|
}
|
|
}
|
|
}
|
|
|
|
void bt_conn_ssp_auth(struct bt_conn *conn, u32_t passkey)
|
|
{
|
|
conn->br.pairing_method = ssp_pair_method(conn);
|
|
|
|
/*
|
|
* If local required security is HIGH then MITM is mandatory.
|
|
* MITM protection is no achievable when SSP 'justworks' is applied.
|
|
*/
|
|
if (conn->required_sec_level > BT_SECURITY_L2 &&
|
|
conn->br.pairing_method == JUST_WORKS) {
|
|
BT_DBG("MITM protection infeasible for required security");
|
|
ssp_confirm_neg_reply(conn);
|
|
return;
|
|
}
|
|
|
|
switch (conn->br.pairing_method) {
|
|
case PASSKEY_CONFIRM:
|
|
atomic_set_bit(conn->flags, BT_CONN_USER);
|
|
bt_auth->passkey_confirm(conn, passkey);
|
|
break;
|
|
case PASSKEY_DISPLAY:
|
|
atomic_set_bit(conn->flags, BT_CONN_USER);
|
|
bt_auth->passkey_display(conn, passkey);
|
|
break;
|
|
case PASSKEY_INPUT:
|
|
atomic_set_bit(conn->flags, BT_CONN_USER);
|
|
bt_auth->passkey_entry(conn);
|
|
break;
|
|
case JUST_WORKS:
|
|
/*
|
|
* When local host works as pairing acceptor and 'justworks'
|
|
* model is applied then notify user about such pairing request.
|
|
* [BT Core 4.2 table 5.7, Vol 3, Part C, 5.2.2.6]
|
|
*/
|
|
if (bt_auth && bt_auth->pairing_confirm &&
|
|
!atomic_test_bit(conn->flags,
|
|
BT_CONN_BR_PAIRING_INITIATOR)) {
|
|
atomic_set_bit(conn->flags, BT_CONN_USER);
|
|
bt_auth->pairing_confirm(conn);
|
|
break;
|
|
}
|
|
ssp_confirm_reply(conn);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int ssp_passkey_reply(struct bt_conn *conn, unsigned int passkey)
|
|
{
|
|
struct bt_hci_cp_user_passkey_reply *cp;
|
|
struct net_buf *buf;
|
|
|
|
BT_DBG("");
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_USER_PASSKEY_REPLY, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
bt_addr_copy(&cp->bdaddr, &conn->br.dst);
|
|
cp->passkey = sys_cpu_to_le32(passkey);
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_USER_PASSKEY_REPLY, buf, NULL);
|
|
}
|
|
|
|
static int ssp_passkey_neg_reply(struct bt_conn *conn)
|
|
{
|
|
struct bt_hci_cp_user_passkey_neg_reply *cp;
|
|
struct net_buf *buf;
|
|
|
|
BT_DBG("");
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_USER_PASSKEY_NEG_REPLY, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
bt_addr_copy(&cp->bdaddr, &conn->br.dst);
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_USER_PASSKEY_NEG_REPLY, buf,
|
|
NULL);
|
|
}
|
|
|
|
static int bt_hci_connect_br_cancel(struct bt_conn *conn)
|
|
{
|
|
struct bt_hci_cp_connect_cancel *cp;
|
|
struct bt_hci_rp_connect_cancel *rp;
|
|
struct net_buf *buf, *rsp;
|
|
int err;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_CONNECT_CANCEL, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
memcpy(&cp->bdaddr, &conn->br.dst, sizeof(cp->bdaddr));
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_CONNECT_CANCEL, buf, &rsp);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
rp = (void *)rsp->data;
|
|
|
|
err = rp->status ? -EIO : 0;
|
|
|
|
net_buf_unref(rsp);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int conn_auth(struct bt_conn *conn)
|
|
{
|
|
struct bt_hci_cp_auth_requested *auth;
|
|
struct net_buf *buf;
|
|
|
|
BT_DBG("");
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_AUTH_REQUESTED, sizeof(*auth));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
auth = net_buf_add(buf, sizeof(*auth));
|
|
auth->handle = sys_cpu_to_le16(conn->handle);
|
|
|
|
atomic_set_bit(conn->flags, BT_CONN_BR_PAIRING_INITIATOR);
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_AUTH_REQUESTED, buf, NULL);
|
|
}
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
#if defined(CONFIG_BT_SMP)
|
|
void bt_conn_identity_resolved(struct bt_conn *conn)
|
|
{
|
|
const bt_addr_le_t *rpa;
|
|
struct bt_conn_cb *cb;
|
|
|
|
if (conn->role == BT_HCI_ROLE_MASTER) {
|
|
rpa = &conn->le.resp_addr;
|
|
} else {
|
|
rpa = &conn->le.init_addr;
|
|
}
|
|
|
|
for (cb = callback_list; cb; cb = cb->_next) {
|
|
if (cb->identity_resolved) {
|
|
cb->identity_resolved(conn, rpa, &conn->le.dst);
|
|
}
|
|
}
|
|
}
|
|
|
|
int bt_conn_le_start_encryption(struct bt_conn *conn, u8_t rand[8],
|
|
u8_t ediv[2], const u8_t *ltk, size_t len)
|
|
{
|
|
struct bt_hci_cp_le_start_encryption *cp;
|
|
struct net_buf *buf;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_START_ENCRYPTION, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
cp->handle = sys_cpu_to_le16(conn->handle);
|
|
memcpy(&cp->rand, rand, sizeof(cp->rand));
|
|
memcpy(&cp->ediv, ediv, sizeof(cp->ediv));
|
|
|
|
memcpy(cp->ltk, ltk, len);
|
|
if (len < sizeof(cp->ltk)) {
|
|
(void)memset(cp->ltk + len, 0, sizeof(cp->ltk) - len);
|
|
}
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_START_ENCRYPTION, buf, NULL);
|
|
}
|
|
#endif /* CONFIG_BT_SMP */
|
|
|
|
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
|
|
u8_t bt_conn_enc_key_size(struct bt_conn *conn)
|
|
{
|
|
//GATT/SR/GAR/BV-04-C
|
|
// if the connection instance is valid
|
|
if (!conn) {
|
|
return 0;
|
|
}
|
|
|
|
if (!conn->encrypt) {
|
|
return 0;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_BREDR) &&
|
|
conn->type == BT_CONN_TYPE_BR) {
|
|
struct bt_hci_cp_read_encryption_key_size *cp;
|
|
struct bt_hci_rp_read_encryption_key_size *rp;
|
|
struct net_buf *buf;
|
|
struct net_buf *rsp;
|
|
u8_t key_size;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_READ_ENCRYPTION_KEY_SIZE,
|
|
sizeof(*cp));
|
|
if (!buf) {
|
|
return 0;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
cp->handle = sys_cpu_to_le16(conn->handle);
|
|
|
|
if (bt_hci_cmd_send_sync(BT_HCI_OP_READ_ENCRYPTION_KEY_SIZE,
|
|
buf, &rsp)) {
|
|
return 0;
|
|
}
|
|
|
|
rp = (void *)rsp->data;
|
|
|
|
key_size = rp->status ? 0 : rp->key_size;
|
|
|
|
net_buf_unref(rsp);
|
|
|
|
return key_size;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SMP)) {
|
|
return conn->le.keys ? conn->le.keys->enc_size : 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bt_conn_security_changed(struct bt_conn *conn, enum bt_security_err err)
|
|
{
|
|
struct bt_conn_cb *cb;
|
|
|
|
for (cb = callback_list; cb; cb = cb->_next) {
|
|
if (cb->security_changed) {
|
|
cb->security_changed(conn, conn->sec_level, err);
|
|
}
|
|
}
|
|
#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST)
|
|
if (!err && conn->sec_level >= BT_SECURITY_L2) {
|
|
bt_keys_update_usage(conn->id, bt_conn_get_dst(conn));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int start_security(struct bt_conn *conn)
|
|
{
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (conn->type == BT_CONN_TYPE_BR) {
|
|
if (atomic_test_bit(conn->flags, BT_CONN_BR_PAIRING)) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (conn->required_sec_level > BT_SECURITY_L3) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (bt_conn_get_io_capa() == BT_IO_NO_INPUT_OUTPUT &&
|
|
conn->required_sec_level > BT_SECURITY_L2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return conn_auth(conn);
|
|
}
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SMP)) {
|
|
return bt_smp_start_security(conn);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec)
|
|
{
|
|
int err;
|
|
|
|
if (conn->state != BT_CONN_CONNECTED) {
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SMP_SC_ONLY) &&
|
|
sec < BT_SECURITY_L4) {
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* nothing to do */
|
|
if (conn->sec_level >= sec || conn->required_sec_level >= sec) {
|
|
return 0;
|
|
}
|
|
|
|
atomic_set_bit_to(conn->flags, BT_CONN_FORCE_PAIR,
|
|
sec & BT_SECURITY_FORCE_PAIR);
|
|
conn->required_sec_level = sec & ~BT_SECURITY_FORCE_PAIR;
|
|
|
|
err = start_security(conn);
|
|
|
|
/* reset required security level in case of error */
|
|
if (err) {
|
|
conn->required_sec_level = conn->sec_level;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
bt_security_t bt_conn_get_security(struct bt_conn *conn)
|
|
{
|
|
return conn->sec_level;
|
|
}
|
|
#else
|
|
bt_security_t bt_conn_get_security(struct bt_conn *conn)
|
|
{
|
|
return BT_SECURITY_L1;
|
|
}
|
|
#endif /* CONFIG_BT_SMP */
|
|
|
|
void bt_conn_cb_register(struct bt_conn_cb *cb)
|
|
{
|
|
cb->_next = callback_list;
|
|
callback_list = cb;
|
|
}
|
|
|
|
void bt_conn_reset_rx_state(struct bt_conn *conn)
|
|
{
|
|
if (!conn->rx_len) {
|
|
return;
|
|
}
|
|
|
|
net_buf_unref(conn->rx);
|
|
conn->rx = NULL;
|
|
conn->rx_len = 0U;
|
|
}
|
|
|
|
void bt_conn_recv(struct bt_conn *conn, struct net_buf *buf, u8_t flags)
|
|
{
|
|
struct bt_l2cap_hdr *hdr;
|
|
u16_t len;
|
|
|
|
/* Make sure we notify any pending TX callbacks before processing
|
|
* new data for this connection.
|
|
*/
|
|
tx_notify(conn);
|
|
|
|
BT_DBG("handle %u len %u flags %02x", conn->handle, buf->len, flags);
|
|
|
|
/* Check packet boundary flags */
|
|
switch (flags) {
|
|
case BT_ACL_START:
|
|
hdr = (void *)buf->data;
|
|
len = sys_le16_to_cpu(hdr->len);
|
|
|
|
BT_DBG("First, len %u final %u", buf->len, len);
|
|
|
|
if (conn->rx_len) {
|
|
BT_ERR("Unexpected first L2CAP frame");
|
|
bt_conn_reset_rx_state(conn);
|
|
}
|
|
|
|
conn->rx_len = (sizeof(*hdr) + len) - buf->len;
|
|
BT_DBG("rx_len %u", conn->rx_len);
|
|
if (conn->rx_len) {
|
|
conn->rx = buf;
|
|
return;
|
|
}
|
|
|
|
break;
|
|
case BT_ACL_CONT:
|
|
if (!conn->rx_len) {
|
|
BT_ERR("Unexpected L2CAP continuation");
|
|
bt_conn_reset_rx_state(conn);
|
|
net_buf_unref(buf);
|
|
return;
|
|
}
|
|
|
|
if (buf->len > conn->rx_len) {
|
|
BT_ERR("L2CAP data overflow");
|
|
bt_conn_reset_rx_state(conn);
|
|
net_buf_unref(buf);
|
|
return;
|
|
}
|
|
|
|
BT_DBG("Cont, len %u rx_len %u", buf->len, conn->rx_len);
|
|
|
|
if (buf->len > net_buf_tailroom(conn->rx)) {
|
|
BT_ERR("Not enough buffer space for L2CAP data");
|
|
bt_conn_reset_rx_state(conn);
|
|
net_buf_unref(buf);
|
|
return;
|
|
}
|
|
|
|
net_buf_add_mem(conn->rx, buf->data, buf->len);
|
|
conn->rx_len -= buf->len;
|
|
net_buf_unref(buf);
|
|
|
|
if (conn->rx_len) {
|
|
return;
|
|
}
|
|
|
|
buf = conn->rx;
|
|
conn->rx = NULL;
|
|
conn->rx_len = 0U;
|
|
|
|
break;
|
|
default:
|
|
BT_ERR("Unexpected ACL flags (0x%02x)", flags);
|
|
bt_conn_reset_rx_state(conn);
|
|
net_buf_unref(buf);
|
|
return;
|
|
}
|
|
|
|
hdr = (void *)buf->data;
|
|
len = sys_le16_to_cpu(hdr->len);
|
|
|
|
if (sizeof(*hdr) + len != buf->len) {
|
|
BT_ERR("ACL len mismatch (%u != %u)", len, buf->len);
|
|
net_buf_unref(buf);
|
|
return;
|
|
}
|
|
|
|
BT_DBG("Successfully parsed %u byte L2CAP packet", buf->len);
|
|
|
|
bt_l2cap_recv(conn, buf);
|
|
}
|
|
|
|
static struct bt_conn_tx *conn_tx_alloc(void)
|
|
{
|
|
/* The TX context always get freed in the system workqueue,
|
|
* so if we're in the same workqueue but there are no immediate
|
|
* contexts available, there's no chance we'll get one by waiting.
|
|
*/
|
|
#if !defined(BFLB_BLE)
|
|
if (k_current_get() == &k_sys_work_q.thread) {
|
|
return k_fifo_get(&free_tx, K_NO_WAIT);
|
|
}
|
|
#endif
|
|
if (IS_ENABLED(CONFIG_BT_DEBUG_CONN)) {
|
|
struct bt_conn_tx *tx = k_fifo_get(&free_tx, K_NO_WAIT);
|
|
|
|
if (tx) {
|
|
return tx;
|
|
}
|
|
|
|
BT_WARN("Unable to get an immediate free conn_tx");
|
|
}
|
|
|
|
return k_fifo_get(&free_tx, K_FOREVER);
|
|
}
|
|
|
|
int bt_conn_send_cb(struct bt_conn *conn, struct net_buf *buf,
|
|
bt_conn_tx_cb_t cb, void *user_data)
|
|
{
|
|
struct bt_conn_tx *tx;
|
|
|
|
BT_DBG("conn handle %u buf len %u cb %p user_data %p", conn->handle,
|
|
buf->len, cb, user_data);
|
|
|
|
if (conn->state != BT_CONN_CONNECTED) {
|
|
BT_ERR("not connected!");
|
|
net_buf_unref(buf);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
if (cb) {
|
|
tx = conn_tx_alloc();
|
|
if (!tx) {
|
|
BT_ERR("Unable to allocate TX context");
|
|
net_buf_unref(buf);
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
/* Verify that we're still connected after blocking */
|
|
if (conn->state != BT_CONN_CONNECTED) {
|
|
BT_WARN("Disconnected while allocating context");
|
|
net_buf_unref(buf);
|
|
tx_free(tx);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
tx->cb = cb;
|
|
tx->user_data = user_data;
|
|
tx->pending_no_cb = 0U;
|
|
|
|
tx_data(buf)->tx = tx;
|
|
} else {
|
|
tx_data(buf)->tx = NULL;
|
|
}
|
|
|
|
#if (BFLB_BT_CO_THREAD)
|
|
if (k_is_current_thread(bt_get_co_thread()))
|
|
bt_conn_process_tx(conn, buf);
|
|
else
|
|
net_buf_put(&conn->tx_queue, buf);
|
|
#if defined(BFLB_BLE)
|
|
k_sem_give(&g_poll_sem);
|
|
#endif
|
|
#else //BFLB_BT_CO_THREAD
|
|
|
|
net_buf_put(&conn->tx_queue, buf);
|
|
#if defined(BFLB_BLE)
|
|
k_sem_give(&g_poll_sem);
|
|
#endif
|
|
#endif //BFLB_BT_CO_THREAD
|
|
return 0;
|
|
}
|
|
|
|
static bool send_frag(struct bt_conn *conn, struct net_buf *buf, u8_t flags,
|
|
bool always_consume)
|
|
{
|
|
struct bt_conn_tx *tx = tx_data(buf)->tx;
|
|
struct bt_hci_acl_hdr *hdr;
|
|
u32_t *pending_no_cb;
|
|
unsigned int key;
|
|
int err;
|
|
|
|
BT_DBG("conn %p buf %p len %u flags 0x%02x", conn, buf, buf->len,
|
|
flags);
|
|
|
|
/* Wait until the controller can accept ACL packets */
|
|
k_sem_take(bt_conn_get_pkts(conn), K_FOREVER);
|
|
|
|
/* Check for disconnection while waiting for pkts_sem */
|
|
if (conn->state != BT_CONN_CONNECTED) {
|
|
goto fail;
|
|
}
|
|
|
|
hdr = net_buf_push(buf, sizeof(*hdr));
|
|
hdr->handle = sys_cpu_to_le16(bt_acl_handle_pack(conn->handle, flags));
|
|
hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr));
|
|
|
|
/* Add to pending, it must be done before bt_buf_set_type */
|
|
key = irq_lock();
|
|
if (tx) {
|
|
sys_slist_append(&conn->tx_pending, &tx->node);
|
|
} else {
|
|
struct bt_conn_tx *tail_tx;
|
|
|
|
tail_tx = (void *)sys_slist_peek_tail(&conn->tx_pending);
|
|
if (tail_tx) {
|
|
pending_no_cb = &tail_tx->pending_no_cb;
|
|
} else {
|
|
pending_no_cb = &conn->pending_no_cb;
|
|
}
|
|
|
|
(*pending_no_cb)++;
|
|
}
|
|
irq_unlock(key);
|
|
|
|
bt_buf_set_type(buf, BT_BUF_ACL_OUT);
|
|
|
|
err = bt_send(buf);
|
|
if (err) {
|
|
BT_ERR("Unable to send to driver (err %d)", err);
|
|
key = irq_lock();
|
|
/* Roll back the pending TX info */
|
|
if (tx) {
|
|
sys_slist_find_and_remove(&conn->tx_pending, &tx->node);
|
|
} else {
|
|
__ASSERT_NO_MSG(*pending_no_cb > 0);
|
|
(*pending_no_cb)--;
|
|
}
|
|
irq_unlock(key);
|
|
goto fail;
|
|
}
|
|
|
|
return true;
|
|
|
|
fail:
|
|
k_sem_give(bt_conn_get_pkts(conn));
|
|
if (tx) {
|
|
tx_free(tx);
|
|
}
|
|
|
|
if (always_consume) {
|
|
net_buf_unref(buf);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static inline u16_t conn_mtu(struct bt_conn *conn)
|
|
{
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (conn->type == BT_CONN_TYPE_BR || !bt_dev.le.mtu) {
|
|
return bt_dev.br.mtu;
|
|
}
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
return bt_dev.le.mtu;
|
|
}
|
|
|
|
static struct net_buf *create_frag(struct bt_conn *conn, struct net_buf *buf)
|
|
{
|
|
struct net_buf *frag;
|
|
u16_t frag_len;
|
|
|
|
#if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0
|
|
frag = bt_conn_create_pdu(&frag_pool, 0);
|
|
#else
|
|
frag = bt_conn_create_pdu(NULL, 0);
|
|
#endif
|
|
|
|
if (conn->state != BT_CONN_CONNECTED) {
|
|
net_buf_unref(frag);
|
|
return NULL;
|
|
}
|
|
|
|
/* Fragments never have a TX completion callback */
|
|
tx_data(frag)->tx = NULL;
|
|
|
|
frag_len = MIN(conn_mtu(conn), net_buf_tailroom(frag));
|
|
|
|
net_buf_add_mem(frag, buf->data, frag_len);
|
|
net_buf_pull(buf, frag_len);
|
|
|
|
return frag;
|
|
}
|
|
|
|
static bool send_buf(struct bt_conn *conn, struct net_buf *buf)
|
|
{
|
|
struct net_buf *frag;
|
|
|
|
BT_DBG("conn %p buf %p len %u", conn, buf, buf->len);
|
|
|
|
/* Send directly if the packet fits the ACL MTU */
|
|
if (buf->len <= conn_mtu(conn)) {
|
|
return send_frag(conn, buf, BT_ACL_START_NO_FLUSH, false);
|
|
}
|
|
|
|
/* Create & enqueue first fragment */
|
|
frag = create_frag(conn, buf);
|
|
if (!frag) {
|
|
return false;
|
|
}
|
|
|
|
if (!send_frag(conn, frag, BT_ACL_START_NO_FLUSH, true)) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Send the fragments. For the last one simply use the original
|
|
* buffer (which works since we've used net_buf_pull on it.
|
|
*/
|
|
while (buf->len > conn_mtu(conn)) {
|
|
frag = create_frag(conn, buf);
|
|
if (!frag) {
|
|
return false;
|
|
}
|
|
|
|
if (!send_frag(conn, frag, BT_ACL_CONT, true)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return send_frag(conn, buf, BT_ACL_CONT, false);
|
|
}
|
|
|
|
static struct k_poll_signal conn_change =
|
|
K_POLL_SIGNAL_INITIALIZER(conn_change);
|
|
|
|
static void conn_cleanup(struct bt_conn *conn)
|
|
{
|
|
struct net_buf *buf;
|
|
|
|
/* Give back any allocated buffers */
|
|
while ((buf = net_buf_get(&conn->tx_queue, K_NO_WAIT))) {
|
|
if (tx_data(buf)->tx) {
|
|
tx_free(tx_data(buf)->tx);
|
|
}
|
|
|
|
net_buf_unref(buf);
|
|
}
|
|
|
|
__ASSERT(sys_slist_is_empty(&conn->tx_pending), "Pending TX packets");
|
|
__ASSERT_NO_MSG(conn->pending_no_cb == 0);
|
|
|
|
bt_conn_reset_rx_state(conn);
|
|
|
|
k_delayed_work_submit(&conn->update_work, K_NO_WAIT);
|
|
|
|
#ifdef BFLB_BLE_PATCH_FREE_ALLOCATED_BUFFER_IN_OS
|
|
k_queue_free(&conn->tx_queue._queue);
|
|
// k_queue_free(&conn->tx_notify._queue);
|
|
conn->tx_queue._queue.hdl = NULL;
|
|
//conn->tx_notify._queue.hdl = NULL;
|
|
if (conn->update_work.timer.timer.hdl)
|
|
k_delayed_work_del_timer(&conn->update_work);
|
|
#endif
|
|
}
|
|
|
|
int bt_conn_prepare_events(struct k_poll_event events[])
|
|
{
|
|
int i, ev_count = 0;
|
|
|
|
BT_DBG("");
|
|
|
|
conn_change.signaled = 0U;
|
|
k_poll_event_init(&events[ev_count++], K_POLL_TYPE_SIGNAL,
|
|
K_POLL_MODE_NOTIFY_ONLY, &conn_change);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
struct bt_conn *conn = &conns[i];
|
|
|
|
if (!atomic_get(&conn->ref)) {
|
|
continue;
|
|
}
|
|
|
|
if (conn->state == BT_CONN_DISCONNECTED &&
|
|
atomic_test_and_clear_bit(conn->flags, BT_CONN_CLEANUP)) {
|
|
conn_cleanup(conn);
|
|
continue;
|
|
}
|
|
|
|
if (conn->state != BT_CONN_CONNECTED) {
|
|
continue;
|
|
}
|
|
|
|
BT_DBG("Adding conn %p to poll list", conn);
|
|
|
|
k_poll_event_init(&events[ev_count],
|
|
K_POLL_TYPE_FIFO_DATA_AVAILABLE,
|
|
K_POLL_MODE_NOTIFY_ONLY,
|
|
&conn->tx_queue);
|
|
events[ev_count++].tag = BT_EVENT_CONN_TX_QUEUE;
|
|
}
|
|
|
|
return ev_count;
|
|
}
|
|
|
|
#if (BFLB_BT_CO_THREAD)
|
|
void bt_conn_process_tx(struct bt_conn *conn, struct net_buf *tx_buf)
|
|
#else
|
|
void bt_conn_process_tx(struct bt_conn *conn)
|
|
#endif
|
|
{
|
|
struct net_buf *buf;
|
|
|
|
BT_DBG("conn %p", conn);
|
|
|
|
if (conn->state == BT_CONN_DISCONNECTED &&
|
|
atomic_test_and_clear_bit(conn->flags, BT_CONN_CLEANUP)) {
|
|
BT_DBG("handle %u disconnected - cleaning up", conn->handle);
|
|
conn_cleanup(conn);
|
|
return;
|
|
}
|
|
#if (BFLB_BT_CO_THREAD)
|
|
if (tx_buf)
|
|
buf = tx_buf;
|
|
else
|
|
buf = net_buf_get(&conn->tx_queue, K_NO_WAIT);
|
|
#else
|
|
/* Get next ACL packet for connection */
|
|
buf = net_buf_get(&conn->tx_queue, K_NO_WAIT);
|
|
#endif
|
|
BT_ASSERT(buf);
|
|
if (!send_buf(conn, buf)) {
|
|
net_buf_unref(buf);
|
|
}
|
|
}
|
|
|
|
struct bt_conn *bt_conn_add_le(u8_t id, const bt_addr_le_t *peer)
|
|
{
|
|
struct bt_conn *conn = conn_new();
|
|
|
|
if (!conn) {
|
|
return NULL;
|
|
}
|
|
|
|
conn->id = id;
|
|
bt_addr_le_copy(&conn->le.dst, peer);
|
|
#if defined(CONFIG_BT_SMP)
|
|
conn->sec_level = BT_SECURITY_L1;
|
|
conn->required_sec_level = BT_SECURITY_L1;
|
|
#endif /* CONFIG_BT_SMP */
|
|
conn->type = BT_CONN_TYPE_LE;
|
|
conn->le.interval_min = BT_GAP_INIT_CONN_INT_MIN;
|
|
conn->le.interval_max = BT_GAP_INIT_CONN_INT_MAX;
|
|
|
|
return conn;
|
|
}
|
|
|
|
static void process_unack_tx(struct bt_conn *conn)
|
|
{
|
|
/* Return any unacknowledged packets */
|
|
while (1) {
|
|
struct bt_conn_tx *tx;
|
|
sys_snode_t *node;
|
|
unsigned int key;
|
|
|
|
key = irq_lock();
|
|
|
|
if (conn->pending_no_cb) {
|
|
conn->pending_no_cb--;
|
|
irq_unlock(key);
|
|
k_sem_give(bt_conn_get_pkts(conn));
|
|
continue;
|
|
}
|
|
|
|
node = sys_slist_get(&conn->tx_pending);
|
|
irq_unlock(key);
|
|
|
|
if (!node) {
|
|
break;
|
|
}
|
|
|
|
tx = CONTAINER_OF(node, struct bt_conn_tx, node);
|
|
|
|
key = irq_lock();
|
|
conn->pending_no_cb = tx->pending_no_cb;
|
|
tx->pending_no_cb = 0U;
|
|
irq_unlock(key);
|
|
|
|
tx_free(tx);
|
|
|
|
k_sem_give(bt_conn_get_pkts(conn));
|
|
}
|
|
}
|
|
|
|
void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
|
|
{
|
|
bt_conn_state_t old_state;
|
|
|
|
BT_DBG("%s -> %s", state2str(conn->state), state2str(state));
|
|
|
|
if (conn->state == state) {
|
|
BT_WARN("no transition");
|
|
return;
|
|
}
|
|
|
|
old_state = conn->state;
|
|
conn->state = state;
|
|
|
|
/* Actions needed for exiting the old state */
|
|
switch (old_state) {
|
|
case BT_CONN_DISCONNECTED:
|
|
/* Take a reference for the first state transition after
|
|
* bt_conn_add_le() and keep it until reaching DISCONNECTED
|
|
* again.
|
|
*/
|
|
bt_conn_ref(conn);
|
|
break;
|
|
case BT_CONN_CONNECT:
|
|
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
|
|
conn->type == BT_CONN_TYPE_LE) {
|
|
k_delayed_work_cancel(&conn->update_work);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Actions needed for entering the new state */
|
|
switch (conn->state) {
|
|
case BT_CONN_CONNECTED:
|
|
if (conn->type == BT_CONN_TYPE_SCO) {
|
|
/* TODO: Notify sco connected */
|
|
break;
|
|
}
|
|
k_fifo_init(&conn->tx_queue, 20);
|
|
k_poll_signal_raise(&conn_change, 0);
|
|
|
|
sys_slist_init(&conn->channels);
|
|
|
|
bt_l2cap_connected(conn);
|
|
notify_connected(conn);
|
|
break;
|
|
case BT_CONN_DISCONNECTED:
|
|
if (conn->type == BT_CONN_TYPE_SCO) {
|
|
/* TODO: Notify sco disconnected */
|
|
bt_conn_unref(conn);
|
|
break;
|
|
}
|
|
/* Notify disconnection and queue a dummy buffer to wake
|
|
* up and stop the tx thread for states where it was
|
|
* running.
|
|
*/
|
|
if (old_state == BT_CONN_CONNECTED ||
|
|
old_state == BT_CONN_DISCONNECT) {
|
|
process_unack_tx(conn);
|
|
tx_notify(conn);
|
|
|
|
/* Cancel Connection Update if it is pending */
|
|
if (conn->type == BT_CONN_TYPE_LE) {
|
|
k_delayed_work_cancel(&conn->update_work);
|
|
}
|
|
|
|
atomic_set_bit(conn->flags, BT_CONN_CLEANUP);
|
|
k_poll_signal_raise(&conn_change, 0);
|
|
/* The last ref will be dropped during cleanup */
|
|
} else if (old_state == BT_CONN_CONNECT) {
|
|
/* conn->err will be set in this case */
|
|
notify_connected(conn);
|
|
bt_conn_unref(conn);
|
|
} else if (old_state == BT_CONN_CONNECT_SCAN) {
|
|
/* this indicate LE Create Connection failed */
|
|
if (conn->err) {
|
|
notify_connected(conn);
|
|
}
|
|
|
|
bt_conn_unref(conn);
|
|
} else if (old_state == BT_CONN_CONNECT_DIR_ADV) {
|
|
/* this indicate Directed advertising stopped */
|
|
if (conn->err) {
|
|
notify_connected(conn);
|
|
}
|
|
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
break;
|
|
case BT_CONN_CONNECT_SCAN:
|
|
break;
|
|
case BT_CONN_CONNECT_DIR_ADV:
|
|
break;
|
|
case BT_CONN_CONNECT:
|
|
if (conn->type == BT_CONN_TYPE_SCO) {
|
|
break;
|
|
}
|
|
/*
|
|
* Timer is needed only for LE. For other link types controller
|
|
* will handle connection timeout.
|
|
*/
|
|
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
|
|
conn->type == BT_CONN_TYPE_LE) {
|
|
k_delayed_work_submit(&conn->update_work, CONN_TIMEOUT);
|
|
}
|
|
|
|
break;
|
|
case BT_CONN_DISCONNECT:
|
|
break;
|
|
default:
|
|
BT_WARN("no valid (%u) state was set", state);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct bt_conn *bt_conn_lookup_handle(u16_t handle)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
if (!atomic_get(&conns[i].ref)) {
|
|
continue;
|
|
}
|
|
|
|
/* We only care about connections with a valid handle */
|
|
if (conns[i].state != BT_CONN_CONNECTED &&
|
|
conns[i].state != BT_CONN_DISCONNECT) {
|
|
continue;
|
|
}
|
|
|
|
if (conns[i].handle == handle) {
|
|
return bt_conn_ref(&conns[i]);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
for (i = 0; i < ARRAY_SIZE(sco_conns); i++) {
|
|
if (!atomic_get(&sco_conns[i].ref)) {
|
|
continue;
|
|
}
|
|
|
|
/* We only care about connections with a valid handle */
|
|
if (sco_conns[i].state != BT_CONN_CONNECTED &&
|
|
sco_conns[i].state != BT_CONN_DISCONNECT) {
|
|
continue;
|
|
}
|
|
|
|
if (sco_conns[i].handle == handle) {
|
|
return bt_conn_ref(&sco_conns[i]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int bt_conn_addr_le_cmp(const struct bt_conn *conn, const bt_addr_le_t *peer)
|
|
{
|
|
/* Check against conn dst address as it may be the identity address */
|
|
if (!bt_addr_le_cmp(peer, &conn->le.dst)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Check against initial connection address */
|
|
if (conn->role == BT_HCI_ROLE_MASTER) {
|
|
return bt_addr_le_cmp(peer, &conn->le.resp_addr);
|
|
}
|
|
|
|
return bt_addr_le_cmp(peer, &conn->le.init_addr);
|
|
}
|
|
|
|
struct bt_conn *bt_conn_lookup_addr_le(u8_t id, const bt_addr_le_t *peer)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
if (!atomic_get(&conns[i].ref)) {
|
|
continue;
|
|
}
|
|
|
|
if (conns[i].type != BT_CONN_TYPE_LE) {
|
|
continue;
|
|
}
|
|
|
|
if (conns[i].id == id &&
|
|
!bt_conn_addr_le_cmp(&conns[i], peer)) {
|
|
return bt_conn_ref(&conns[i]);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct bt_conn *bt_conn_lookup_state_le(const bt_addr_le_t *peer,
|
|
const bt_conn_state_t state)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
if (!atomic_get(&conns[i].ref)) {
|
|
continue;
|
|
}
|
|
|
|
if (conns[i].type != BT_CONN_TYPE_LE) {
|
|
continue;
|
|
}
|
|
|
|
if (peer && bt_conn_addr_le_cmp(&conns[i], peer)) {
|
|
continue;
|
|
}
|
|
|
|
if (conns[i].state == state) {
|
|
return bt_conn_ref(&conns[i]);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void bt_conn_foreach(int type, void (*func)(struct bt_conn *conn, void *data),
|
|
void *data)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
if (!atomic_get(&conns[i].ref)) {
|
|
continue;
|
|
}
|
|
|
|
if (!(conns[i].type & type)) {
|
|
continue;
|
|
}
|
|
|
|
func(&conns[i], data);
|
|
}
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (type & BT_CONN_TYPE_SCO) {
|
|
for (i = 0; i < ARRAY_SIZE(sco_conns); i++) {
|
|
if (!atomic_get(&sco_conns[i].ref)) {
|
|
continue;
|
|
}
|
|
|
|
func(&sco_conns[i], data);
|
|
}
|
|
}
|
|
#endif /* defined(CONFIG_BT_BREDR) */
|
|
}
|
|
|
|
static void disconnect_all(struct bt_conn *conn, void *data)
|
|
{
|
|
u8_t *id = (u8_t *)data;
|
|
|
|
if (conn->id == *id && conn->state == BT_CONN_CONNECTED) {
|
|
bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
|
}
|
|
}
|
|
|
|
void bt_conn_disconnect_all(u8_t id)
|
|
{
|
|
bt_conn_foreach(BT_CONN_TYPE_ALL, disconnect_all, &id);
|
|
}
|
|
|
|
struct bt_conn *bt_conn_ref(struct bt_conn *conn)
|
|
{
|
|
atomic_inc(&conn->ref);
|
|
|
|
BT_DBG("handle %u ref %u", conn->handle, atomic_get(&conn->ref));
|
|
|
|
return conn;
|
|
}
|
|
|
|
void bt_conn_unref(struct bt_conn *conn)
|
|
{
|
|
atomic_dec(&conn->ref);
|
|
|
|
BT_DBG("handle %u ref %u", conn->handle, atomic_get(&conn->ref));
|
|
}
|
|
|
|
const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn)
|
|
{
|
|
return &conn->le.dst;
|
|
}
|
|
|
|
int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info)
|
|
{
|
|
info->type = conn->type;
|
|
info->role = conn->role;
|
|
info->id = conn->id;
|
|
|
|
switch (conn->type) {
|
|
case BT_CONN_TYPE_LE:
|
|
info->le.dst = &conn->le.dst;
|
|
info->le.src = &bt_dev.id_addr[conn->id];
|
|
if (conn->role == BT_HCI_ROLE_MASTER) {
|
|
info->le.local = &conn->le.init_addr;
|
|
info->le.remote = &conn->le.resp_addr;
|
|
} else {
|
|
info->le.local = &conn->le.resp_addr;
|
|
info->le.remote = &conn->le.init_addr;
|
|
}
|
|
info->le.interval = conn->le.interval;
|
|
info->le.latency = conn->le.latency;
|
|
info->le.timeout = conn->le.timeout;
|
|
return 0;
|
|
#if defined(CONFIG_BT_BREDR)
|
|
case BT_CONN_TYPE_BR:
|
|
info->br.dst = &conn->br.dst;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int bt_conn_get_remote_dev_info(struct bt_conn_info *info)
|
|
{
|
|
int link_num = 0;
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
if (!atomic_get(&conns[i].ref)) {
|
|
continue;
|
|
}
|
|
bt_conn_get_info(&conns[i], &info[link_num]);
|
|
link_num++;
|
|
}
|
|
|
|
return link_num;
|
|
}
|
|
|
|
static int bt_hci_disconnect(struct bt_conn *conn, u8_t reason)
|
|
{
|
|
struct net_buf *buf;
|
|
struct bt_hci_cp_disconnect *disconn;
|
|
int err;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_DISCONNECT, sizeof(*disconn));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
disconn = net_buf_add(buf, sizeof(*disconn));
|
|
disconn->handle = sys_cpu_to_le16(conn->handle);
|
|
disconn->reason = reason;
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_DISCONNECT, buf, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_STACK_PTS)
|
|
int pts_bt_conn_le_param_update(struct bt_conn *conn,
|
|
const struct bt_le_conn_param *param)
|
|
{
|
|
BT_DBG("conn %p features 0x%02x params (%d-%d %d %d)", conn,
|
|
conn->le.features[0], param->interval_min,
|
|
param->interval_max, param->latency, param->timeout);
|
|
|
|
/* Check if there's a need to update conn params */
|
|
if (conn->le.interval >= param->interval_min &&
|
|
conn->le.interval <= param->interval_max &&
|
|
conn->le.latency == param->latency &&
|
|
conn->le.timeout == param->timeout) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
/* Cancel any pending update */
|
|
k_delayed_work_cancel(&conn->update_work);
|
|
|
|
return bt_l2cap_update_conn_param(conn, param);
|
|
}
|
|
#endif
|
|
int bt_conn_le_param_update(struct bt_conn *conn,
|
|
const struct bt_le_conn_param *param)
|
|
{
|
|
BT_DBG("conn %p features 0x%02x params (%d-%d %d %d)", conn,
|
|
conn->le.features[0], param->interval_min,
|
|
param->interval_max, param->latency, param->timeout);
|
|
|
|
/* Check if there's a need to update conn params */
|
|
if (conn->le.interval >= param->interval_min &&
|
|
conn->le.interval <= param->interval_max &&
|
|
conn->le.latency == param->latency &&
|
|
conn->le.timeout == param->timeout) {
|
|
atomic_clear_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET);
|
|
return -EALREADY;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
|
|
conn->role == BT_CONN_ROLE_MASTER) {
|
|
return send_conn_le_param_update(conn, param);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) {
|
|
/* if slave conn param update timer expired just send request */
|
|
if (atomic_test_bit(conn->flags, BT_CONN_SLAVE_PARAM_UPDATE)) {
|
|
return send_conn_le_param_update(conn, param);
|
|
}
|
|
|
|
/* store new conn params to be used by update timer */
|
|
conn->le.interval_min = param->interval_min;
|
|
conn->le.interval_max = param->interval_max;
|
|
conn->le.pending_latency = param->latency;
|
|
conn->le.pending_timeout = param->timeout;
|
|
atomic_set_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_conn_disconnect(struct bt_conn *conn, u8_t reason)
|
|
{
|
|
/* Disconnection is initiated by us, so auto connection shall
|
|
* be disabled. Otherwise the passive scan would be enabled
|
|
* and we could send LE Create Connection as soon as the remote
|
|
* starts advertising.
|
|
*/
|
|
#if !defined(CONFIG_BT_WHITELIST)
|
|
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
|
|
conn->type == BT_CONN_TYPE_LE) {
|
|
bt_le_set_auto_conn(&conn->le.dst, NULL);
|
|
}
|
|
#endif /* !defined(CONFIG_BT_WHITELIST) */
|
|
|
|
switch (conn->state) {
|
|
case BT_CONN_CONNECT_SCAN:
|
|
conn->err = reason;
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
|
if (IS_ENABLED(CONFIG_BT_CENTRAL)) {
|
|
bt_le_scan_update(false);
|
|
}
|
|
return 0;
|
|
case BT_CONN_CONNECT_DIR_ADV:
|
|
conn->err = reason;
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) {
|
|
/* User should unref connection object when receiving
|
|
* error in connection callback.
|
|
*/
|
|
return bt_le_adv_stop();
|
|
}
|
|
return 0;
|
|
case BT_CONN_CONNECT:
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (conn->type == BT_CONN_TYPE_BR) {
|
|
return bt_hci_connect_br_cancel(conn);
|
|
}
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
if (IS_ENABLED(CONFIG_BT_CENTRAL)) {
|
|
k_delayed_work_cancel(&conn->update_work);
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CONN_CANCEL,
|
|
NULL, NULL);
|
|
}
|
|
|
|
return 0;
|
|
case BT_CONN_CONNECTED:
|
|
return bt_hci_disconnect(conn, reason);
|
|
case BT_CONN_DISCONNECT:
|
|
return 0;
|
|
case BT_CONN_DISCONNECTED:
|
|
default:
|
|
return -ENOTCONN;
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_BT_CENTRAL)
|
|
static void bt_conn_set_param_le(struct bt_conn *conn,
|
|
const struct bt_le_conn_param *param)
|
|
{
|
|
conn->le.interval_min = param->interval_min;
|
|
conn->le.interval_max = param->interval_max;
|
|
conn->le.latency = param->latency;
|
|
conn->le.timeout = param->timeout;
|
|
|
|
#if defined(CONFIG_BT_STACK_PTS)
|
|
conn->le.own_adder_type = param->own_address_type;
|
|
#endif
|
|
}
|
|
|
|
#if defined(CONFIG_BT_WHITELIST)
|
|
int bt_conn_create_auto_le(const struct bt_le_conn_param *param)
|
|
{
|
|
struct bt_conn *conn;
|
|
int err;
|
|
|
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!bt_le_conn_params_valid(param)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_AUTO_CONN)) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
if (!bt_dev.le.wl_entries) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Don't start initiator if we have general discovery procedure. */
|
|
conn = bt_conn_lookup_state_le(NULL, BT_CONN_CONNECT_SCAN);
|
|
if (conn) {
|
|
bt_conn_unref(conn);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Don't start initiator if we have direct discovery procedure. */
|
|
conn = bt_conn_lookup_state_le(NULL, BT_CONN_CONNECT);
|
|
if (conn) {
|
|
bt_conn_unref(conn);
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = bt_le_auto_conn(param);
|
|
if (err) {
|
|
BT_ERR("Failed to start whitelist scan");
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_conn_create_auto_stop(void)
|
|
{
|
|
int err;
|
|
|
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_AUTO_CONN)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = bt_le_auto_conn_cancel();
|
|
if (err) {
|
|
BT_ERR("Failed to stop initiator");
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* defined(CONFIG_BT_WHITELIST) */
|
|
|
|
struct bt_conn *bt_conn_create_le(const bt_addr_le_t *peer,
|
|
const struct bt_le_conn_param *param)
|
|
{
|
|
struct bt_conn *conn;
|
|
bt_addr_le_t dst;
|
|
|
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!bt_le_conn_params_valid(param)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_WHITELIST) &&
|
|
atomic_test_bit(bt_dev.flags, BT_DEV_AUTO_CONN)) {
|
|
return NULL;
|
|
}
|
|
|
|
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, peer);
|
|
if (conn) {
|
|
switch (conn->state) {
|
|
case BT_CONN_CONNECT_SCAN:
|
|
bt_conn_set_param_le(conn, param);
|
|
return conn;
|
|
case BT_CONN_CONNECT:
|
|
case BT_CONN_CONNECTED:
|
|
return conn;
|
|
case BT_CONN_DISCONNECTED:
|
|
BT_WARN("Found valid but disconnected conn object");
|
|
goto start_scan;
|
|
default:
|
|
bt_conn_unref(conn);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (peer->type == BT_ADDR_LE_PUBLIC_ID ||
|
|
peer->type == BT_ADDR_LE_RANDOM_ID) {
|
|
bt_addr_le_copy(&dst, peer);
|
|
dst.type -= BT_ADDR_LE_PUBLIC_ID;
|
|
} else {
|
|
bt_addr_le_copy(&dst, bt_lookup_id_addr(BT_ID_DEFAULT, peer));
|
|
}
|
|
|
|
/* Only default identity supported for now */
|
|
conn = bt_conn_add_le(BT_ID_DEFAULT, &dst);
|
|
if (!conn) {
|
|
return NULL;
|
|
}
|
|
|
|
start_scan:
|
|
bt_conn_set_param_le(conn, param);
|
|
|
|
bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN);
|
|
|
|
bt_le_scan_update(true);
|
|
|
|
return conn;
|
|
}
|
|
|
|
#if !defined(CONFIG_BT_WHITELIST)
|
|
int bt_le_set_auto_conn(const bt_addr_le_t *addr,
|
|
const struct bt_le_conn_param *param)
|
|
{
|
|
struct bt_conn *conn;
|
|
|
|
if (param && !bt_le_conn_params_valid(param)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Only default identity is supported */
|
|
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr);
|
|
if (!conn) {
|
|
conn = bt_conn_add_le(BT_ID_DEFAULT, addr);
|
|
if (!conn) {
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
if (param) {
|
|
bt_conn_set_param_le(conn, param);
|
|
|
|
if (!atomic_test_and_set_bit(conn->flags,
|
|
BT_CONN_AUTO_CONNECT)) {
|
|
bt_conn_ref(conn);
|
|
}
|
|
} else {
|
|
if (atomic_test_and_clear_bit(conn->flags,
|
|
BT_CONN_AUTO_CONNECT)) {
|
|
bt_conn_unref(conn);
|
|
if (conn->state == BT_CONN_CONNECT_SCAN) {
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (conn->state == BT_CONN_DISCONNECTED &&
|
|
atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
|
|
if (param) {
|
|
bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN);
|
|
}
|
|
bt_le_scan_update(false);
|
|
}
|
|
|
|
bt_conn_unref(conn);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* !defined(CONFIG_BT_WHITELIST) */
|
|
#endif /* CONFIG_BT_CENTRAL */
|
|
|
|
#if defined(CONFIG_BT_PERIPHERAL)
|
|
struct bt_conn *bt_conn_create_slave_le(const bt_addr_le_t *peer,
|
|
const struct bt_le_adv_param *param)
|
|
{
|
|
int err;
|
|
struct bt_conn *conn;
|
|
struct bt_le_adv_param param_int;
|
|
|
|
memcpy(¶m_int, param, sizeof(param_int));
|
|
param_int.options |= (BT_LE_ADV_OPT_CONNECTABLE |
|
|
BT_LE_ADV_OPT_ONE_TIME);
|
|
|
|
conn = bt_conn_lookup_addr_le(param->id, peer);
|
|
if (conn) {
|
|
switch (conn->state) {
|
|
case BT_CONN_CONNECT_DIR_ADV:
|
|
/* Handle the case when advertising is stopped with
|
|
* bt_le_adv_stop function
|
|
*/
|
|
err = bt_le_adv_start_internal(¶m_int, NULL, 0,
|
|
NULL, 0, peer);
|
|
if (err && (err != -EALREADY)) {
|
|
BT_WARN("Directed advertising could not be"
|
|
" started: %d",
|
|
err);
|
|
bt_conn_unref(conn);
|
|
return NULL;
|
|
}
|
|
__attribute__((fallthrough));
|
|
case BT_CONN_CONNECT:
|
|
case BT_CONN_CONNECTED:
|
|
return conn;
|
|
case BT_CONN_DISCONNECTED:
|
|
BT_WARN("Found valid but disconnected conn object");
|
|
goto start_adv;
|
|
default:
|
|
bt_conn_unref(conn);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
conn = bt_conn_add_le(param->id, peer);
|
|
if (!conn) {
|
|
return NULL;
|
|
}
|
|
|
|
start_adv:
|
|
bt_conn_set_state(conn, BT_CONN_CONNECT_DIR_ADV);
|
|
|
|
err = bt_le_adv_start_internal(¶m_int, NULL, 0, NULL, 0, peer);
|
|
if (err) {
|
|
BT_WARN("Directed advertising could not be started: %d", err);
|
|
|
|
bt_conn_unref(conn);
|
|
return NULL;
|
|
}
|
|
|
|
return conn;
|
|
}
|
|
#endif /* CONFIG_BT_PERIPHERAL */
|
|
|
|
int bt_conn_le_conn_update(struct bt_conn *conn,
|
|
const struct bt_le_conn_param *param)
|
|
{
|
|
struct hci_cp_le_conn_update *conn_update;
|
|
struct net_buf *buf;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_CONN_UPDATE,
|
|
sizeof(*conn_update));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
conn_update = net_buf_add(buf, sizeof(*conn_update));
|
|
(void)memset(conn_update, 0, sizeof(*conn_update));
|
|
conn_update->handle = sys_cpu_to_le16(conn->handle);
|
|
conn_update->conn_interval_min = sys_cpu_to_le16(param->interval_min);
|
|
conn_update->conn_interval_max = sys_cpu_to_le16(param->interval_max);
|
|
conn_update->conn_latency = sys_cpu_to_le16(param->latency);
|
|
conn_update->supervision_timeout = sys_cpu_to_le16(param->timeout);
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CONN_UPDATE, buf, NULL);
|
|
}
|
|
|
|
struct net_buf *bt_conn_create_pdu_timeout(struct net_buf_pool *pool,
|
|
size_t reserve, s32_t timeout)
|
|
{
|
|
struct net_buf *buf;
|
|
|
|
/*
|
|
* PDU must not be allocated from ISR as we block with 'K_FOREVER'
|
|
* during the allocation
|
|
*/
|
|
__ASSERT_NO_MSG(!k_is_in_isr());
|
|
|
|
if (!pool) {
|
|
pool = &acl_tx_pool;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_DEBUG_CONN)) {
|
|
buf = net_buf_alloc(pool, K_NO_WAIT);
|
|
if (!buf) {
|
|
BT_WARN("Unable to allocate buffer with K_NO_WAIT");
|
|
buf = net_buf_alloc(pool, timeout);
|
|
}
|
|
} else {
|
|
buf = net_buf_alloc(pool, timeout);
|
|
}
|
|
|
|
if (!buf) {
|
|
BT_WARN("Unable to allocate buffer: timeout %d", timeout);
|
|
return NULL;
|
|
}
|
|
|
|
reserve += sizeof(struct bt_hci_acl_hdr) + BT_BUF_RESERVE;
|
|
net_buf_reserve(buf, reserve);
|
|
|
|
return buf;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
|
|
int bt_conn_auth_cb_register(const struct bt_conn_auth_cb *cb)
|
|
{
|
|
if (!cb) {
|
|
bt_auth = NULL;
|
|
return 0;
|
|
}
|
|
|
|
if (bt_auth) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
/* The cancel callback must always be provided if the app provides
|
|
* interactive callbacks.
|
|
*/
|
|
if (!cb->cancel &&
|
|
(cb->passkey_display || cb->passkey_entry || cb->passkey_confirm ||
|
|
#if defined(CONFIG_BT_BREDR)
|
|
cb->pincode_entry ||
|
|
#endif
|
|
cb->pairing_confirm)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
bt_auth = cb;
|
|
return 0;
|
|
}
|
|
|
|
int bt_conn_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey)
|
|
{
|
|
if (!bt_auth) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) {
|
|
bt_smp_auth_passkey_entry(conn, passkey);
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (conn->type == BT_CONN_TYPE_BR) {
|
|
/* User entered passkey, reset user state. */
|
|
if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) {
|
|
return -EPERM;
|
|
}
|
|
|
|
if (conn->br.pairing_method == PASSKEY_INPUT) {
|
|
return ssp_passkey_reply(conn, passkey);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int bt_conn_auth_passkey_confirm(struct bt_conn *conn)
|
|
{
|
|
if (!bt_auth) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SMP) &&
|
|
conn->type == BT_CONN_TYPE_LE) {
|
|
return bt_smp_auth_passkey_confirm(conn);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (conn->type == BT_CONN_TYPE_BR) {
|
|
/* Allow user confirm passkey value, then reset user state. */
|
|
if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) {
|
|
return -EPERM;
|
|
}
|
|
|
|
return ssp_confirm_reply(conn);
|
|
}
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int bt_conn_auth_cancel(struct bt_conn *conn)
|
|
{
|
|
if (!bt_auth) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) {
|
|
return bt_smp_auth_cancel(conn);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
if (conn->type == BT_CONN_TYPE_BR) {
|
|
/* Allow user cancel authentication, then reset user state. */
|
|
if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) {
|
|
return -EPERM;
|
|
}
|
|
|
|
switch (conn->br.pairing_method) {
|
|
case JUST_WORKS:
|
|
case PASSKEY_CONFIRM:
|
|
return ssp_confirm_neg_reply(conn);
|
|
case PASSKEY_INPUT:
|
|
return ssp_passkey_neg_reply(conn);
|
|
case PASSKEY_DISPLAY:
|
|
return bt_conn_disconnect(conn,
|
|
BT_HCI_ERR_AUTH_FAIL);
|
|
case LEGACY:
|
|
return pin_code_neg_reply(&conn->br.dst);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int bt_conn_auth_pairing_confirm(struct bt_conn *conn)
|
|
{
|
|
if (!bt_auth) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (conn->type) {
|
|
#if defined(CONFIG_BT_SMP)
|
|
case BT_CONN_TYPE_LE:
|
|
return bt_smp_auth_pairing_confirm(conn);
|
|
#endif /* CONFIG_BT_SMP */
|
|
#if defined(CONFIG_BT_BREDR)
|
|
case BT_CONN_TYPE_BR:
|
|
return ssp_confirm_reply(conn);
|
|
#endif /* CONFIG_BT_BREDR */
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR */
|
|
|
|
u8_t bt_conn_index(struct bt_conn *conn)
|
|
{
|
|
u8_t index = conn - conns;
|
|
|
|
__ASSERT(index < CONFIG_BT_MAX_CONN, "Invalid bt_conn pointer");
|
|
return index;
|
|
}
|
|
|
|
struct bt_conn *bt_conn_lookup_id(u8_t id)
|
|
{
|
|
struct bt_conn *conn;
|
|
|
|
if (id >= ARRAY_SIZE(conns)) {
|
|
return NULL;
|
|
}
|
|
|
|
conn = &conns[id];
|
|
|
|
if (!atomic_get(&conn->ref)) {
|
|
return NULL;
|
|
}
|
|
|
|
return bt_conn_ref(conn);
|
|
}
|
|
|
|
int bt_conn_init(void)
|
|
{
|
|
#if defined(CONFIG_BT_SMP)
|
|
int err;
|
|
#endif
|
|
int i;
|
|
|
|
#if defined(BFLB_BLE)
|
|
#if defined(BFLB_DYNAMIC_ALLOC_MEM)
|
|
#if (BFLB_STATIC_ALLOC_MEM)
|
|
net_buf_init(ACL_TX, &acl_tx_pool, CONFIG_BT_L2CAP_TX_BUF_COUNT, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), NULL);
|
|
#else
|
|
net_buf_init(&acl_tx_pool, CONFIG_BT_L2CAP_TX_BUF_COUNT, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), NULL);
|
|
#endif
|
|
#if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0
|
|
#if (BFLB_STATIC_ALLOC_MEM)
|
|
net_buf_init(FRAG, &frag_pool, CONFIG_BT_L2CAP_TX_FRAG_COUNT, FRAG_SIZE, NULL);
|
|
#else
|
|
net_buf_init(&frag_pool, CONFIG_BT_L2CAP_TX_FRAG_COUNT, FRAG_SIZE, NULL);
|
|
#endif
|
|
#endif
|
|
#else //BFLB_DYNAMIC_ALLOC_MEM
|
|
struct net_buf_pool num_complete_pool;
|
|
struct net_buf_pool acl_tx_pool;
|
|
#if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0
|
|
struct net_buf_pool frag_pool;
|
|
#endif
|
|
#endif //BFLB_DYNAMIC_ALLOC_MEM
|
|
k_fifo_init(&free_tx, 20);
|
|
#endif
|
|
for (i = 0; i < ARRAY_SIZE(conn_tx); i++) {
|
|
k_fifo_put(&free_tx, &conn_tx[i]);
|
|
}
|
|
|
|
bt_att_init();
|
|
|
|
#if defined(CONFIG_BT_SMP)
|
|
err = bt_smp_init();
|
|
if (err) {
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
bt_l2cap_init();
|
|
|
|
/* Initialize background scan */
|
|
if (IS_ENABLED(CONFIG_BT_CENTRAL)) {
|
|
for (i = 0; i < ARRAY_SIZE(conns); i++) {
|
|
struct bt_conn *conn = &conns[i];
|
|
|
|
if (!atomic_get(&conn->ref)) {
|
|
continue;
|
|
}
|
|
|
|
if (atomic_test_bit(conn->flags,
|
|
BT_CONN_AUTO_CONNECT)) {
|
|
/* Only the default identity is supported */
|
|
conn->id = BT_ID_DEFAULT;
|
|
bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|