1090 lines
28 KiB
C
1090 lines
28 KiB
C
/****************************************************************************
|
|
* wireless/bluetooth/bt_conn.c
|
|
* Bluetooth connection handling.
|
|
*
|
|
* Copyright (C) 2018 Gregory Nutt. All rights reserved.
|
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
|
*
|
|
* Ported from the Intel/Zephyr arduino101_firmware_source-v1.tar package
|
|
* where the code was released with a compatible 3-clause BSD license:
|
|
*
|
|
* Copyright (c) 2016, Intel Corporation
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/kthread.h>
|
|
#include <nuttx/mm/iob.h>
|
|
#include <nuttx/wireless/bluetooth/bt_hci.h>
|
|
#include <nuttx/wireless/bluetooth/bt_core.h>
|
|
|
|
#include "bt_atomic.h"
|
|
#include "bt_queue.h"
|
|
#include "bt_hcicore.h"
|
|
#include "bt_conn.h"
|
|
#include "bt_l2cap.h"
|
|
#include "bt_keys.h"
|
|
#include "bt_smp.h"
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct bt_conn_handoff_s
|
|
{
|
|
sem_t sync_sem;
|
|
FAR struct bt_conn_s *conn;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static struct bt_conn_s g_conns[CONFIG_BLUETOOTH_MAX_CONN];
|
|
static struct bt_conn_handoff_s g_conn_handoff =
|
|
{
|
|
SEM_INITIALIZER(1),
|
|
NULL
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_DEBUG_WIRELESS_INFO
|
|
static const char *state2str(enum bt_conn_state_e state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case BT_CONN_DISCONNECTED:
|
|
return "disconnected";
|
|
|
|
case BT_CONN_CONNECT_SCAN:
|
|
return "connect-scan";
|
|
|
|
case BT_CONN_CONNECT:
|
|
return "connect";
|
|
|
|
case BT_CONN_CONNECTED:
|
|
return "connected";
|
|
|
|
case BT_CONN_DISCONNECT:
|
|
return "disconnect";
|
|
|
|
default:
|
|
return "(unknown)";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void bt_conn_reset_rx_state(FAR struct bt_conn_s *conn)
|
|
{
|
|
if (!conn->rx_len)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bt_buf_release(conn->rx);
|
|
conn->rx = NULL;
|
|
conn->rx_len = 0;
|
|
}
|
|
|
|
static int conn_tx_kthread(int argc, FAR char *argv[])
|
|
{
|
|
FAR struct bt_conn_s *conn;
|
|
FAR struct bt_buf_s *buf;
|
|
struct mq_attr attr;
|
|
int ret;
|
|
|
|
/* Get the connection instance */
|
|
|
|
conn = g_conn_handoff.conn;
|
|
DEBUGASSERT(conn != NULL);
|
|
nxsem_post(&g_conn_handoff.sync_sem);
|
|
|
|
wlinfo("Started for handle %u\n", conn->handle);
|
|
|
|
while (conn->state == BT_CONN_CONNECTED)
|
|
{
|
|
/* Wait until the controller can accept ACL packets */
|
|
|
|
wlinfo("calling nxsem_wait_uninterruptible()\n");
|
|
|
|
ret = nxsem_wait_uninterruptible(&g_btdev.le_pkts_sem);
|
|
if (ret < 0)
|
|
{
|
|
wlerr("nxsem_wait_uninterruptible() failed: %d\n", ret);
|
|
break;
|
|
}
|
|
|
|
/* Check for disconnection */
|
|
|
|
if (conn->state != BT_CONN_CONNECTED)
|
|
{
|
|
nxsem_post(&g_btdev.le_pkts_sem);
|
|
break;
|
|
}
|
|
|
|
/* Get next ACL packet for connection */
|
|
|
|
ret = bt_queue_receive(conn->tx_queue, &buf);
|
|
DEBUGASSERT(ret >= 0 && buf != NULL);
|
|
UNUSED(ret);
|
|
|
|
if (conn->state != BT_CONN_CONNECTED)
|
|
{
|
|
nxsem_post(&g_btdev.le_pkts_sem);
|
|
bt_buf_release(buf);
|
|
break;
|
|
}
|
|
|
|
wlinfo("passing buf %p len %u to driver\n", buf, buf->len);
|
|
g_btdev.btdev->send(g_btdev.btdev, buf);
|
|
bt_buf_release(buf);
|
|
}
|
|
|
|
wlinfo("handle %u disconnected - cleaning up\n", conn->handle);
|
|
|
|
/* Give back any allocated buffers */
|
|
|
|
do
|
|
{
|
|
buf = NULL;
|
|
|
|
/* Make sure the thread is not blocked forever on an empty queue.
|
|
* SIOCBTCONNECT will fail if preceding SIOCBTDISCONNECT does not
|
|
* result in a successful termination of this thread.
|
|
*/
|
|
|
|
ret = mq_getattr(conn->tx_queue, &attr);
|
|
if (ret != OK)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (attr.mq_curmsgs == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ret = bt_queue_receive(conn->tx_queue, &buf);
|
|
if (ret >= 0)
|
|
{
|
|
DEBUGASSERT(buf != NULL);
|
|
bt_buf_release(buf);
|
|
}
|
|
}
|
|
while (ret >= OK);
|
|
|
|
bt_conn_reset_rx_state(conn);
|
|
|
|
wlinfo("handle %u exiting\n", conn->handle);
|
|
|
|
/* Release reference taken when thread was created */
|
|
|
|
bt_conn_release(conn);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
static int bt_hci_disconnect(FAR struct bt_conn_s *conn, uint8_t reason)
|
|
{
|
|
FAR struct bt_buf_s *buf;
|
|
FAR struct bt_hci_cp_disconnect_s *disconn;
|
|
int err;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_DISCONNECT, sizeof(*disconn));
|
|
if (!buf)
|
|
{
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
disconn = bt_buf_extend(buf, sizeof(*disconn));
|
|
disconn->handle = BT_HOST2LE16(conn->handle);
|
|
disconn->reason = reason;
|
|
|
|
err = bt_hci_cmd_send(BT_HCI_OP_DISCONNECT, buf);
|
|
if (err)
|
|
{
|
|
return err;
|
|
}
|
|
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECT);
|
|
return 0;
|
|
}
|
|
|
|
static int bt_hci_connect_le_cancel(FAR struct bt_conn_s *conn)
|
|
{
|
|
int err;
|
|
|
|
err = bt_hci_cmd_send(BT_HCI_OP_LE_CREATE_CONN_CANCEL, NULL);
|
|
if (err)
|
|
{
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_receive
|
|
*
|
|
* Description:
|
|
* Receive packets from the HCI core on a registered connection.
|
|
*
|
|
* Input Parameters:
|
|
* conn - The registered connection
|
|
* buf - The buffer structure containing the packet
|
|
* flags - Packet boundary flags
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void bt_conn_receive(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf,
|
|
uint8_t flags)
|
|
{
|
|
FAR struct bt_l2cap_hdr_s *hdr;
|
|
uint16_t len;
|
|
|
|
wlinfo("handle %u len %u flags %02x\n", conn->handle, buf->len, flags);
|
|
|
|
/* Check packet boundary flags */
|
|
|
|
switch (flags)
|
|
{
|
|
case BT_HCI_ACL_NEW:
|
|
|
|
/* First packet */
|
|
|
|
hdr = (void *)buf->data;
|
|
len = BT_LE162HOST(hdr->len);
|
|
|
|
wlinfo("First, len %u final %u\n", buf->len, len);
|
|
|
|
if (conn->rx_len)
|
|
{
|
|
wlerr("ERROR: Unexpected first L2CAP frame\n");
|
|
bt_conn_reset_rx_state(conn);
|
|
}
|
|
|
|
conn->rx_len = (sizeof(*hdr) + len) - buf->len;
|
|
wlinfo("rx_len %u\n", conn->rx_len);
|
|
if (conn->rx_len)
|
|
{
|
|
conn->rx = buf;
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case BT_HCI_ACL_CONTINUATION:
|
|
|
|
/* Continuation */
|
|
|
|
if (!conn->rx_len)
|
|
{
|
|
wlerr("ERROR: Unexpected L2CAP continuation\n");
|
|
bt_conn_reset_rx_state(conn);
|
|
bt_buf_release(buf);
|
|
return;
|
|
}
|
|
|
|
if (buf->len > conn->rx_len)
|
|
{
|
|
wlerr("ERROR: L2CAP data overflow\n");
|
|
bt_conn_reset_rx_state(conn);
|
|
bt_buf_release(buf);
|
|
return;
|
|
}
|
|
|
|
wlinfo("Cont, len %u rx_len %u\n", buf->len, conn->rx_len);
|
|
|
|
if (buf->len > bt_buf_tailroom(conn->rx))
|
|
{
|
|
wlerr("ERROR: Not enough buffer space for L2CAP data\n");
|
|
bt_conn_reset_rx_state(conn);
|
|
bt_buf_release(buf);
|
|
return;
|
|
}
|
|
|
|
memcpy(bt_buf_extend(conn->rx, buf->len), buf->data, buf->len);
|
|
conn->rx_len -= buf->len;
|
|
bt_buf_release(buf);
|
|
|
|
if (conn->rx_len)
|
|
{
|
|
return;
|
|
}
|
|
|
|
buf = conn->rx;
|
|
conn->rx = NULL;
|
|
conn->rx_len = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
wlerr("ERROR: Unexpected ACL flags (0x%02x)\n", flags);
|
|
bt_conn_reset_rx_state(conn);
|
|
bt_buf_release(buf);
|
|
return;
|
|
}
|
|
|
|
hdr = (void *)buf->data;
|
|
len = BT_LE162HOST(hdr->len);
|
|
|
|
if (sizeof(*hdr) + len != buf->len)
|
|
{
|
|
wlerr("ERROR: ACL len mismatch (%u != %u)\n", len, buf->len);
|
|
bt_buf_release(buf);
|
|
return;
|
|
}
|
|
|
|
wlinfo("Successfully parsed %u byte L2CAP packet\n", buf->len);
|
|
|
|
bt_l2cap_receive(conn, buf);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_send
|
|
*
|
|
* Description:
|
|
* Send data over a connection
|
|
*
|
|
* Input Parameters:
|
|
* conn - The registered connection
|
|
* buf - The buffer structure containing the packet to be sent
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void bt_conn_send(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf)
|
|
{
|
|
FAR struct bt_hci_acl_hdr_s *hdr;
|
|
sq_queue_t fraglist;
|
|
uint16_t len;
|
|
uint16_t remaining = buf->len;
|
|
FAR uint8_t *ptr;
|
|
|
|
DEBUGASSERT(conn != NULL && buf != NULL);
|
|
|
|
sq_init(&fraglist);
|
|
|
|
wlinfo("conn handle %u buf len %u\n", conn->handle, buf->len);
|
|
|
|
if (conn->state != BT_CONN_CONNECTED)
|
|
{
|
|
wlerr("ERROR: not connected!\n");
|
|
return;
|
|
}
|
|
|
|
len = remaining;
|
|
if (len > g_btdev.le_mtu)
|
|
{
|
|
len = g_btdev.le_mtu;
|
|
}
|
|
|
|
hdr = bt_buf_provide(buf, sizeof(*hdr));
|
|
hdr->handle = BT_HOST2LE16(conn->handle);
|
|
hdr->len = BT_HOST2LE16(len);
|
|
|
|
buf->len -= remaining - len;
|
|
ptr = bt_buf_tail(buf);
|
|
|
|
/* Add the fragment to the end of the list */
|
|
|
|
sq_addlast((FAR sq_entry_t *)buf, &fraglist);
|
|
remaining -= len;
|
|
|
|
while (remaining)
|
|
{
|
|
buf = bt_l2cap_create_pdu(conn);
|
|
|
|
len = remaining;
|
|
if (len < g_btdev.le_mtu)
|
|
{
|
|
len = g_btdev.le_mtu;
|
|
}
|
|
|
|
/* Copy from original buffer */
|
|
|
|
memcpy(bt_buf_extend(buf, len), ptr, len);
|
|
ptr += len;
|
|
|
|
hdr = bt_buf_provide(buf, sizeof(*hdr));
|
|
hdr->handle = BT_HOST2LE16(conn->handle | (1 << 12));
|
|
hdr->len = BT_HOST2LE16(len);
|
|
|
|
/* Add the fragment to the end of the list */
|
|
|
|
sq_addlast((FAR sq_entry_t *)buf, &fraglist);
|
|
remaining -= len;
|
|
}
|
|
|
|
/* Then send each fragment in the correct order */
|
|
|
|
while ((buf = (FAR struct bt_buf_s *)sq_remfirst(&fraglist)) != NULL)
|
|
{
|
|
bt_queue_send(conn->tx_queue, buf, BT_NORMAL_PRIO);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_add
|
|
*
|
|
* Description:
|
|
* Add a new connection
|
|
*
|
|
* Input Parameters:
|
|
* peer - The address of the Bluetooth peer
|
|
* role - Either BT_HCI_ROLE_MASTER or BT_HCI_ROLE_SLAVE
|
|
*
|
|
* Returned Value:
|
|
* A reference to the new connection structure is returned on success.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct bt_conn_s *bt_conn_add(FAR const bt_addr_le_t *peer,
|
|
uint8_t role)
|
|
{
|
|
FAR struct bt_conn_s *conn = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++)
|
|
{
|
|
if (!bt_addr_le_cmp(&g_conns[i].dst, BT_ADDR_LE_ANY))
|
|
{
|
|
conn = &g_conns[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!conn)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
memset(conn, 0, sizeof(*conn));
|
|
|
|
bt_atomic_set(&conn->ref, 1);
|
|
conn->role = role;
|
|
bt_addr_le_copy(&conn->dst, peer);
|
|
|
|
return conn;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_set_state
|
|
*
|
|
* Description:
|
|
* Set connection object in certain state and perform actions related to
|
|
* state change.
|
|
*
|
|
* Input Parameters:
|
|
* conn - The connection whose state will be changed.
|
|
* state - The new state of the connection.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void bt_conn_set_state(FAR struct bt_conn_s *conn,
|
|
enum bt_conn_state_e state)
|
|
{
|
|
enum bt_conn_state_e old_state;
|
|
|
|
wlinfo("%s -> %s\n", state2str(conn->state), state2str(state));
|
|
|
|
if (conn->state == state)
|
|
{
|
|
wlwarn("no transition\n");
|
|
return;
|
|
}
|
|
|
|
old_state = conn->state;
|
|
conn->state = state;
|
|
|
|
/* Take a reference for the first state transition after bt_conn_add() and
|
|
* keep it until reaching DISCONNECTED again.
|
|
*/
|
|
|
|
if (old_state == BT_CONN_DISCONNECTED)
|
|
{
|
|
bt_conn_addref(conn);
|
|
}
|
|
|
|
switch (conn->state)
|
|
{
|
|
case BT_CONN_CONNECTED:
|
|
{
|
|
pid_t pid;
|
|
int ret;
|
|
|
|
ret = bt_queue_open(BT_CONN_TX, O_RDWR | O_CREAT,
|
|
CONFIG_BLUETOOTH_TXCONN_NMSGS,
|
|
&conn->tx_queue);
|
|
DEBUGASSERT(ret >= 0 && g_btdev.tx_queue != 0);
|
|
UNUSED(ret);
|
|
|
|
/* Get exclusive access to the handoff structure. The count will
|
|
* be zero when we complete this.
|
|
*/
|
|
|
|
ret = nxsem_wait_uninterruptible(&g_conn_handoff.sync_sem);
|
|
if (ret >= 0)
|
|
{
|
|
/* Start the Tx connection kernel thread */
|
|
|
|
g_conn_handoff.conn = bt_conn_addref(conn);
|
|
pid = kthread_create("BT Conn Tx",
|
|
CONFIG_BLUETOOTH_TXCONN_PRIORITY,
|
|
CONFIG_BLUETOOTH_TXCONN_STACKSIZE,
|
|
conn_tx_kthread, NULL);
|
|
DEBUGASSERT(pid > 0);
|
|
UNUSED(pid);
|
|
|
|
/* Take the semaphore again. This will force us to wait with
|
|
* the sem_count at -1. It will be zero again when we
|
|
* continue.
|
|
*/
|
|
|
|
ret = nxsem_wait_uninterruptible(&g_conn_handoff.sync_sem);
|
|
nxsem_post(&g_conn_handoff.sync_sem);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BT_CONN_DISCONNECTED:
|
|
|
|
/* Send 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)
|
|
{
|
|
bt_queue_send(conn->tx_queue, bt_buf_alloc(BT_DUMMY, NULL, 0),
|
|
BT_NORMAL_PRIO);
|
|
}
|
|
|
|
/* Release the reference we took for the very first state
|
|
* transition.
|
|
*/
|
|
|
|
bt_conn_release(conn);
|
|
break;
|
|
|
|
case BT_CONN_CONNECT_SCAN:
|
|
case BT_CONN_CONNECT:
|
|
case BT_CONN_DISCONNECT:
|
|
break;
|
|
|
|
default:
|
|
wlwarn("no valid (%u) state was set\n", state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_lookup_handle
|
|
*
|
|
* Description:
|
|
* Look up an existing connection
|
|
*
|
|
* Input Parameters:
|
|
* handle - The handle to be used to perform the lookup
|
|
*
|
|
* Returned Value:
|
|
* A reference to the connection state instance is returned on success.
|
|
* NULL is returned if the connection is not found. On success, the
|
|
* caller gets a new reference to the connection object which must be
|
|
* released with bt_conn_release() once done using the connection.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct bt_conn_s *bt_conn_lookup_handle(uint16_t handle)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++)
|
|
{
|
|
/* We only care about connections with a valid handle */
|
|
|
|
if (g_conns[i].state != BT_CONN_CONNECTED &&
|
|
g_conns[i].state != BT_CONN_DISCONNECT)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (g_conns[i].handle == handle)
|
|
{
|
|
return bt_conn_addref(&g_conns[i]);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_lookup_addr_le
|
|
*
|
|
* Description:
|
|
* Look up an existing connection based on the remote address.
|
|
*
|
|
* Input Parameters:
|
|
* peer - Remote address.
|
|
*
|
|
* Returned Value:
|
|
* A reference to the connection state instance is returned on success.
|
|
* NULL is returned if the connection is not found. On success, the
|
|
* caller gets a new reference to the connection object which must be
|
|
* released with bt_conn_release() once done using the connection.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct bt_conn_s *bt_conn_lookup_addr_le(FAR const bt_addr_le_t * peer)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++)
|
|
{
|
|
if (!bt_addr_le_cmp(peer, &g_conns[i].dst))
|
|
{
|
|
return bt_conn_addref(&g_conns[i]);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_lookup_state
|
|
*
|
|
* Description:
|
|
* Look up a connection state. For BT_ADDR_LE_ANY, returns the first
|
|
* connection with the specific state
|
|
*
|
|
* Input Parameters:
|
|
* peer - The peer address to match
|
|
* state - The connection state to match
|
|
*
|
|
* Returned Value:
|
|
* A reference to the connection state instance is returned on success.
|
|
* NULL is returned if the connection is not found. On success, the
|
|
* caller gets a new reference to the connection object which must be
|
|
* released with bt_conn_release() once done using the connection.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct bt_conn_s *bt_conn_lookup_state(FAR const bt_addr_le_t * peer,
|
|
enum bt_conn_state_e state)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++)
|
|
{
|
|
if (!bt_addr_le_cmp(&g_conns[i].dst, BT_ADDR_LE_ANY))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (bt_addr_le_cmp(peer, BT_ADDR_LE_ANY) &&
|
|
bt_addr_le_cmp(peer, &g_conns[i].dst))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (g_conns[i].state == state)
|
|
{
|
|
return bt_conn_addref(&g_conns[i]);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_addref
|
|
*
|
|
* Description:
|
|
* Increment the reference count of a connection object.
|
|
*
|
|
* Input Parameters:
|
|
* conn - Connection object.
|
|
*
|
|
* Returned Value:
|
|
* Connection object with incremented reference count.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct bt_conn_s *bt_conn_addref(FAR struct bt_conn_s *conn)
|
|
{
|
|
bt_atomic_incr(&conn->ref);
|
|
|
|
wlinfo("handle %u ref %u\n", conn->handle, bt_atomic_get(&conn->ref));
|
|
|
|
return conn;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_release
|
|
*
|
|
* Description:
|
|
* Decrement the reference count of a connection object.
|
|
*
|
|
* Input Parameters:
|
|
* conn - Connection object.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void bt_conn_release(FAR struct bt_conn_s *conn)
|
|
{
|
|
bt_atomic_t old_ref;
|
|
|
|
old_ref = bt_atomic_decr(&conn->ref);
|
|
|
|
wlinfo("handle %u ref %u\n", conn->handle, bt_atomic_get(&conn->ref));
|
|
|
|
if (old_ref > 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bt_addr_le_copy(&conn->dst, BT_ADDR_LE_ANY);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_get_dst
|
|
*
|
|
* Description:
|
|
* Get destination (peer) address of a connection.
|
|
*
|
|
* Input Parameters:
|
|
* conn - Connection object.
|
|
*
|
|
* Returned Value:
|
|
* Destination address.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR const bt_addr_le_t *bt_conn_get_dst(FAR const struct bt_conn_s *conn)
|
|
{
|
|
return &conn->dst;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_security
|
|
*
|
|
* Description:
|
|
* This function enable security (encryption) for a connection. If device
|
|
* is already paired with sufficiently strong key encryption will be
|
|
* enabled. If link is already encrypted with sufficiently strong key this
|
|
* function does nothing.
|
|
*
|
|
* If device is not paired pairing will be initiated. If device is paired
|
|
* and keys are too weak but input output capabilities allow for strong
|
|
* enough keys pairing will be initiated.
|
|
*
|
|
* This function may return error if required level of security is not
|
|
* possible to achieve due to local or remote device limitation (eg input
|
|
* output capabilities).
|
|
*
|
|
* Input Parameters:
|
|
* conn - Connection object.
|
|
* sec - Requested security level.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success or negative error
|
|
*
|
|
****************************************************************************/
|
|
|
|
int bt_conn_security(FAR struct bt_conn_s *conn, enum bt_security_e sec)
|
|
{
|
|
FAR struct bt_keys_s *keys;
|
|
|
|
if (conn->state != BT_CONN_CONNECTED)
|
|
{
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
/* Nothing to do */
|
|
|
|
if (sec == BT_SECURITY_LOW)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* For now we only support JustWorks */
|
|
|
|
if (sec > BT_SECURITY_MEDIUM)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (conn->encrypt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (conn->role == BT_HCI_ROLE_SLAVE)
|
|
{
|
|
return bt_smp_send_security_req(conn);
|
|
}
|
|
|
|
keys = bt_keys_find(BT_KEYS_LTK, &conn->dst);
|
|
if (keys)
|
|
{
|
|
return bt_conn_le_start_encryption(conn, keys->ltk.rand,
|
|
keys->ltk.ediv, keys->ltk.val);
|
|
}
|
|
|
|
return bt_smp_send_pairing_req(conn);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name:bt_conn_set_auto_conn
|
|
*
|
|
* Description:
|
|
* This function enables/disables automatic connection initiation.
|
|
* Every time the device looses the connection with peer, this connection
|
|
* will be re-established if connectible advertisement from peer is
|
|
* received.
|
|
*
|
|
* Input Parameters:
|
|
* conn - Existing connection object.
|
|
* auto_conn - boolean value. If true, auto connect is enabled, if false,
|
|
* auto connect is disabled.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void bt_conn_set_auto_conn(FAR struct bt_conn_s *conn, bool auto_conn)
|
|
{
|
|
if (auto_conn)
|
|
{
|
|
bt_atomic_setbit(conn->flags, BT_CONN_AUTO_CONNECT);
|
|
}
|
|
else
|
|
{
|
|
bt_atomic_clrbit(conn->flags, BT_CONN_AUTO_CONNECT);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_disconnect
|
|
*
|
|
* Description:
|
|
* Disconnect an active connection with the specified reason code or cancel
|
|
* pending outgoing connection.
|
|
*
|
|
* Input Parameters:
|
|
* conn - Connection to disconnect.
|
|
* reason - Reason code for the disconnection.
|
|
*
|
|
* Returned Value:
|
|
* Zero on success or (negative) error code on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int bt_conn_disconnect(FAR struct bt_conn_s *conn, uint8_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.
|
|
*/
|
|
|
|
bt_conn_set_auto_conn(conn, false);
|
|
|
|
switch (conn->state)
|
|
{
|
|
case BT_CONN_CONNECT_SCAN:
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
|
bt_le_scan_update();
|
|
return 0;
|
|
|
|
case BT_CONN_CONNECT:
|
|
return bt_hci_connect_le_cancel(conn);
|
|
|
|
case BT_CONN_CONNECTED:
|
|
return bt_hci_disconnect(conn, reason);
|
|
|
|
case BT_CONN_DISCONNECT:
|
|
return 0;
|
|
|
|
case BT_CONN_DISCONNECTED:
|
|
default:
|
|
return -ENOTCONN;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_create_le
|
|
*
|
|
* Description:
|
|
* Allows initiate new LE link to remote peer using its address.
|
|
* Returns a new reference that the the caller is responsible for managing.
|
|
*
|
|
* Input Parameters:
|
|
* peer - Remote address.
|
|
*
|
|
* Returned Value:
|
|
* Valid connection object on success or NULL otherwise.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct bt_conn_s *bt_conn_create_le(FAR const bt_addr_le_t *peer)
|
|
{
|
|
FAR struct bt_conn_s *conn;
|
|
|
|
/* First check if this connection exists and that it is in a proper
|
|
* state.
|
|
*/
|
|
|
|
conn = bt_conn_lookup_addr_le(peer);
|
|
if (conn != NULL)
|
|
{
|
|
switch (conn->state)
|
|
{
|
|
case BT_CONN_CONNECT_SCAN:
|
|
case BT_CONN_CONNECT:
|
|
case BT_CONN_CONNECTED:
|
|
return conn;
|
|
|
|
default:
|
|
bt_conn_release(conn);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* No.. the connection does not exist. Create it assuming MASTER role
|
|
* and put it in the BT_CONNECT_SCAN state.
|
|
*/
|
|
|
|
conn = bt_conn_add(peer, BT_HCI_ROLE_MASTER);
|
|
if (!conn)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN);
|
|
bt_le_scan_update();
|
|
return conn;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: bt_conn_le_start_encryption
|
|
*
|
|
* Description:
|
|
* See the HCI start encryption command.
|
|
*
|
|
* NOTE: rand and ediv should be in BT order.
|
|
*
|
|
* Input Parameters:
|
|
* conn - The connection to send the command on.
|
|
* rand, ediv - Values to use for the encryption key
|
|
* ltk -
|
|
*
|
|
* Returned Value:
|
|
* Zero is returned on success; a negated errno value is returned on any
|
|
* failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int bt_conn_le_start_encryption(FAR struct bt_conn_s *conn, uint64_t rand,
|
|
uint16_t ediv, FAR const uint8_t *ltk)
|
|
{
|
|
FAR struct bt_hci_cp_le_start_encryption_s *cp;
|
|
FAR struct bt_buf_s *buf;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_START_ENCRYPTION, sizeof(*cp));
|
|
if (!buf)
|
|
{
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = bt_buf_extend(buf, sizeof(*cp));
|
|
cp->handle = BT_HOST2LE16(conn->handle);
|
|
cp->rand = rand;
|
|
cp->ediv = ediv;
|
|
memcpy(cp->ltk, ltk, sizeof(cp->ltk));
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_START_ENCRYPTION, buf, NULL);
|
|
}
|
|
|
|
int bt_conn_le_conn_update(FAR struct bt_conn_s *conn, uint16_t min,
|
|
uint16_t max, uint16_t latency, uint16_t timeout)
|
|
{
|
|
FAR struct hci_cp_le_conn_update_s *conn_update;
|
|
FAR struct bt_buf_s *buf;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_CONN_UPDATE, sizeof(*conn_update));
|
|
if (!buf)
|
|
{
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
conn_update = bt_buf_extend(buf,
|
|
sizeof(*conn_update));
|
|
memset(conn_update, 0, sizeof(*conn_update));
|
|
conn_update->handle = BT_HOST2LE16(conn->handle);
|
|
conn_update->conn_interval_min = BT_HOST2LE16(min);
|
|
conn_update->conn_interval_max = BT_HOST2LE16(max);
|
|
conn_update->conn_latency = BT_HOST2LE16(latency);
|
|
conn_update->supervision_timeout = BT_HOST2LE16(timeout);
|
|
|
|
return bt_hci_cmd_send(BT_HCI_OP_LE_CONN_UPDATE, buf);
|
|
}
|