6a3c2aded6
* Simplify EINTR/ECANCEL error handling 1. Add semaphore uninterruptible wait function 2 .Replace semaphore wait loop with a single uninterruptible wait 3. Replace all sem_xxx to nxsem_xxx * Unify the void cast usage 1. Remove void cast for function because many place ignore the returned value witout cast 2. Replace void cast for variable with UNUSED macro
1071 lines
28 KiB
C
1071 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 HOLDER 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\n");
|
|
|
|
nxsem_wait_uninterruptible(&g_btdev.le_pkts_sem);
|
|
|
|
/* 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 0x02:
|
|
/* 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 0x01:
|
|
/* 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.
|
|
*/
|
|
|
|
nxsem_wait_uninterruptible(&g_conn_handoff.sync_sem);
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
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);
|
|
}
|