zephyr/subsys/bluetooth/host/conn.c
Dennis Grijalva b122685eff bluetooth: conn: Fix compiler warning
When compiling conn.c using arm-none-eabi-gcc version 11.3.1 20220712
with the -Wmaybe-uninitialized flag a warning is emitted due to
pending_no_cb not being initialized. I'm not sure if initializing it to
NULL is the "correct" fix, but it's certainly not any worse then it being
uninitialized, and it fixes the warning.

Signed-off-by: Dennis Grijalva <dennisgrijalva@meta.com>
2023-10-08 18:17:22 +03:00

3426 lines
77 KiB
C

/* conn.c - Bluetooth connection handling */
/*
* Copyright (c) 2015-2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/iterable_sections.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/sys/slist.h>
#include <zephyr/debug/stack.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/direction.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/drivers/bluetooth/hci_driver.h>
#include <zephyr/bluetooth/att.h>
#include "common/assert.h"
#include "common/bt_str.h"
#include "addr_internal.h"
#include "hci_core.h"
#include "id.h"
#include "adv.h"
#include "conn_internal.h"
#include "l2cap_internal.h"
#include "keys.h"
#include "smp.h"
#include "ssp.h"
#include "att_internal.h"
#include "iso_internal.h"
#include "direction_internal.h"
#define LOG_LEVEL CONFIG_BT_CONN_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_conn);
struct tx_meta {
struct bt_conn_tx *tx;
/* This flag indicates if the current buffer has already been partially
* sent to the controller (ie, the next fragments should be sent as
* continuations).
*/
bool is_cont;
/* Indicates whether the ISO PDU contains a timestamp */
bool iso_has_ts;
};
BUILD_ASSERT(sizeof(struct tx_meta) == CONFIG_BT_CONN_TX_USER_DATA_SIZE,
"User data size is wrong!");
#define tx_data(buf) ((struct tx_meta *)net_buf_user_data(buf))
K_FIFO_DEFINE(free_tx);
static void tx_free(struct bt_conn_tx *tx);
static void conn_tx_destroy(struct bt_conn *conn, struct bt_conn_tx *tx)
{
__ASSERT_NO_MSG(tx);
bt_conn_tx_cb_t cb = tx->cb;
void *user_data = tx->user_data;
/* Free up TX metadata before calling callback in case the callback
* tries to allocate metadata
*/
tx_free(tx);
cb(conn, user_data, -ESHUTDOWN);
}
#if defined(CONFIG_BT_CONN_TX)
static void tx_complete_work(struct k_work *work);
#endif /* CONFIG_BT_CONN_TX */
/* Group Connected BT_CONN only in this */
#if defined(CONFIG_BT_CONN)
/* Peripheral timeout to initialize Connection Parameter Update procedure */
#define CONN_UPDATE_TIMEOUT K_MSEC(CONFIG_BT_CONN_PARAM_UPDATE_TIMEOUT)
static void deferred_work(struct k_work *work);
static void notify_connected(struct bt_conn *conn);
static struct bt_conn acl_conns[CONFIG_BT_MAX_CONN];
NET_BUF_POOL_DEFINE(acl_tx_pool, CONFIG_BT_L2CAP_TX_BUF_COUNT,
BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
#if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0
/* 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,
BT_BUF_ACL_SIZE(CONFIG_BT_BUF_ACL_TX_SIZE),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
#endif /* CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0 */
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
const struct bt_conn_auth_cb *bt_auth;
sys_slist_t bt_auth_info_cbs = SYS_SLIST_STATIC_INIT(&bt_auth_info_cbs);
#endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR */
static struct bt_conn_cb *callback_list;
static struct bt_conn_tx conn_tx[CONFIG_BT_CONN_TX_MAX];
#if defined(CONFIG_BT_BREDR)
static int bt_hci_connect_br_cancel(struct bt_conn *conn);
static struct bt_conn sco_conns[CONFIG_BT_MAX_SCO_CONN];
#endif /* CONFIG_BT_BREDR */
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_ISO)
extern struct bt_conn iso_conns[CONFIG_BT_ISO_MAX_CHAN];
/* Callback TX buffers for ISO */
static struct bt_conn_tx iso_tx[CONFIG_BT_ISO_TX_BUF_COUNT];
int bt_conn_iso_init(void)
{
for (size_t i = 0; i < ARRAY_SIZE(iso_tx); i++) {
k_fifo_put(&free_tx, &iso_tx[i]);
}
return 0;
}
#endif /* CONFIG_BT_ISO */
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.acl_mtu) {
return &bt_dev.br.pkts;
}
#endif /* CONFIG_BT_BREDR */
#if defined(CONFIG_BT_ISO)
/* Use ISO pkts semaphore if LE Read Buffer Size command returned
* dedicated ISO buffers.
*/
if (conn->type == BT_CONN_TYPE_ISO) {
if (bt_dev.le.iso_mtu && bt_dev.le.iso_limit != 0) {
return &bt_dev.le.iso_pkts;
}
return NULL;
}
#endif /* CONFIG_BT_ISO */
#if defined(CONFIG_BT_CONN)
if (bt_dev.le.acl_mtu) {
return &bt_dev.le.acl_pkts;
}
#endif /* CONFIG_BT_CONN */
return NULL;
}
static inline const char *state2str(bt_conn_state_t state)
{
switch (state) {
case BT_CONN_DISCONNECTED:
return "disconnected";
case BT_CONN_DISCONNECT_COMPLETE:
return "disconnect-complete";
case BT_CONN_CONNECTING_SCAN:
return "connecting-scan";
case BT_CONN_CONNECTING_DIR_ADV:
return "connecting-dir-adv";
case BT_CONN_CONNECTING_ADV:
return "connecting-adv";
case BT_CONN_CONNECTING_AUTO:
return "connecting-auto";
case BT_CONN_CONNECTING:
return "connecting";
case BT_CONN_CONNECTED:
return "connected";
case BT_CONN_DISCONNECTING:
return "disconnecting";
default:
return "(unknown)";
}
}
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)
{
LOG_DBG("conn %p", conn);
while (1) {
struct bt_conn_tx *tx = NULL;
unsigned int key;
bt_conn_tx_cb_t cb;
void *user_data;
key = irq_lock();
if (!sys_slist_is_empty(&conn->tx_complete)) {
tx = CONTAINER_OF(sys_slist_get_not_empty(&conn->tx_complete),
struct bt_conn_tx, node);
}
irq_unlock(key);
if (!tx) {
return;
}
LOG_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, 0);
}
}
struct bt_conn *bt_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));
#if defined(CONFIG_BT_CONN)
k_work_init_delayable(&conn->deferred_work, deferred_work);
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_CONN_TX)
k_work_init(&conn->tx_complete_work, tx_complete_work);
#endif /* CONFIG_BT_CONN_TX */
return conn;
}
void bt_conn_reset_rx_state(struct bt_conn *conn)
{
if (!conn->rx) {
return;
}
net_buf_unref(conn->rx);
conn->rx = NULL;
}
static void bt_acl_recv(struct bt_conn *conn, struct net_buf *buf,
uint8_t flags)
{
uint16_t acl_total_len;
/* Check packet boundary flags */
switch (flags) {
case BT_ACL_START:
if (conn->rx) {
LOG_ERR("Unexpected first L2CAP frame");
bt_conn_reset_rx_state(conn);
}
LOG_DBG("First, len %u final %u", buf->len,
(buf->len < sizeof(uint16_t)) ? 0 : sys_get_le16(buf->data));
conn->rx = buf;
break;
case BT_ACL_CONT:
if (!conn->rx) {
LOG_ERR("Unexpected L2CAP continuation");
bt_conn_reset_rx_state(conn);
net_buf_unref(buf);
return;
}
if (!buf->len) {
LOG_DBG("Empty ACL_CONT");
net_buf_unref(buf);
return;
}
if (buf->len > net_buf_tailroom(conn->rx)) {
LOG_ERR("Not enough buffer space for L2CAP data");
/* Frame is not complete but we still pass it to L2CAP
* so that it may handle error on protocol level
* eg disconnect channel.
*/
bt_l2cap_recv(conn, conn->rx, false);
conn->rx = NULL;
net_buf_unref(buf);
return;
}
net_buf_add_mem(conn->rx, buf->data, buf->len);
net_buf_unref(buf);
break;
default:
/* BT_ACL_START_NO_FLUSH and BT_ACL_COMPLETE are not allowed on
* LE-U from Controller to Host.
* Only BT_ACL_POINT_TO_POINT is supported.
*/
LOG_ERR("Unexpected ACL flags (0x%02x)", flags);
bt_conn_reset_rx_state(conn);
net_buf_unref(buf);
return;
}
if (conn->rx->len < sizeof(uint16_t)) {
/* Still not enough data received to retrieve the L2CAP header
* length field.
*/
return;
}
acl_total_len = sys_get_le16(conn->rx->data) + sizeof(struct bt_l2cap_hdr);
if (conn->rx->len < acl_total_len) {
/* L2CAP frame not complete. */
return;
}
if (conn->rx->len > acl_total_len) {
LOG_ERR("ACL len mismatch (%u > %u)", conn->rx->len, acl_total_len);
bt_conn_reset_rx_state(conn);
return;
}
/* L2CAP frame complete. */
buf = conn->rx;
conn->rx = NULL;
LOG_DBG("Successfully parsed %u byte L2CAP packet", buf->len);
bt_l2cap_recv(conn, buf, true);
}
void bt_conn_recv(struct bt_conn *conn, struct net_buf *buf, uint8_t flags)
{
/* Make sure we notify any pending TX callbacks before processing
* new data for this connection.
*/
tx_notify(conn);
LOG_DBG("handle %u len %u flags %02x", conn->handle, buf->len, flags);
if ((IS_ENABLED(CONFIG_BT_ISO_UNICAST) ||
IS_ENABLED(CONFIG_BT_ISO_SYNC_RECEIVER)) &&
conn->type == BT_CONN_TYPE_ISO) {
bt_iso_recv(conn, buf, flags);
return;
} else if (IS_ENABLED(CONFIG_BT_CONN)) {
bt_acl_recv(conn, buf, flags);
} else {
__ASSERT(false, "Invalid connection type %u", conn->type);
}
}
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 (k_current_get() == &k_sys_work_q.thread) {
return k_fifo_get(&free_tx, K_NO_WAIT);
}
if (IS_ENABLED(CONFIG_BT_CONN_LOG_LEVEL_DBG)) {
struct bt_conn_tx *tx = k_fifo_get(&free_tx, K_NO_WAIT);
if (tx) {
return tx;
}
LOG_WRN("Unable to get an immediate free conn_tx");
}
return k_fifo_get(&free_tx, K_FOREVER);
}
int bt_conn_send_iso_cb(struct bt_conn *conn, struct net_buf *buf,
bt_conn_tx_cb_t cb, bool has_ts)
{
if (buf->user_data_size < CONFIG_BT_CONN_TX_USER_DATA_SIZE) {
LOG_ERR("not enough room in user_data %d < %d",
buf->user_data_size,
CONFIG_BT_CONN_TX_USER_DATA_SIZE);
return -EINVAL;
}
/* Necessary for setting the TS_Flag bit when we pop the buffer from the
* send queue. The flag needs to be set before adding the buffer to the queue.
*/
tx_data(buf)->iso_has_ts = has_ts;
int err = bt_conn_send_cb(conn, buf, cb, NULL);
if (err) {
return err;
}
return 0;
}
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;
LOG_DBG("conn handle %u buf len %u cb %p user_data %p", conn->handle, buf->len, cb,
user_data);
if (buf->user_data_size < CONFIG_BT_CONN_TX_USER_DATA_SIZE) {
LOG_ERR("not enough room in user_data %d < %d",
buf->user_data_size,
CONFIG_BT_CONN_TX_USER_DATA_SIZE);
return -EINVAL;
}
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("not connected!");
return -ENOTCONN;
}
if (cb) {
tx = conn_tx_alloc();
if (!tx) {
LOG_DBG("Unable to allocate TX context");
return -ENOBUFS;
}
/* Verify that we're still connected after blocking */
if (conn->state != BT_CONN_CONNECTED) {
LOG_WRN("Disconnected while allocating context");
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;
}
tx_data(buf)->is_cont = false;
net_buf_put(&conn->tx_queue, buf);
return 0;
}
enum {
FRAG_START,
FRAG_CONT,
FRAG_SINGLE,
FRAG_END
};
static int send_acl(struct bt_conn *conn, struct net_buf *buf, uint8_t flags)
{
struct bt_hci_acl_hdr *hdr;
switch (flags) {
case FRAG_START:
case FRAG_SINGLE:
flags = BT_ACL_START_NO_FLUSH;
break;
case FRAG_CONT:
case FRAG_END:
flags = BT_ACL_CONT;
break;
default:
return -EINVAL;
}
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));
bt_buf_set_type(buf, BT_BUF_ACL_OUT);
return bt_send(buf);
}
static int send_iso(struct bt_conn *conn, struct net_buf *buf, uint8_t flags)
{
struct bt_hci_iso_hdr *hdr;
bool ts;
switch (flags) {
case FRAG_START:
flags = BT_ISO_START;
break;
case FRAG_CONT:
flags = BT_ISO_CONT;
break;
case FRAG_SINGLE:
flags = BT_ISO_SINGLE;
break;
case FRAG_END:
flags = BT_ISO_END;
break;
default:
return -EINVAL;
}
hdr = net_buf_push(buf, sizeof(*hdr));
ts = tx_data(buf)->iso_has_ts &&
(flags == BT_ISO_START || flags == BT_ISO_SINGLE);
hdr->handle = sys_cpu_to_le16(bt_iso_handle_pack(conn->handle, flags, ts));
hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr));
bt_buf_set_type(buf, BT_BUF_ISO_OUT);
return bt_send(buf);
}
static inline uint16_t conn_mtu(struct bt_conn *conn)
{
#if defined(CONFIG_BT_BREDR)
if (conn->type == BT_CONN_TYPE_BR ||
(conn->type != BT_CONN_TYPE_ISO && !bt_dev.le.acl_mtu)) {
return bt_dev.br.mtu;
}
#endif /* CONFIG_BT_BREDR */
#if defined(CONFIG_BT_ISO)
if (conn->type == BT_CONN_TYPE_ISO) {
return bt_dev.le.iso_mtu;
}
#endif /* CONFIG_BT_ISO */
#if defined(CONFIG_BT_CONN)
return bt_dev.le.acl_mtu;
#else
return 0;
#endif /* CONFIG_BT_CONN */
}
static int do_send_frag(struct bt_conn *conn, struct net_buf *buf, uint8_t flags)
{
struct bt_conn_tx *tx = tx_data(buf)->tx;
uint32_t *pending_no_cb = NULL;
unsigned int key;
int err = 0;
/* Check for disconnection while waiting for pkts_sem */
if (conn->state != BT_CONN_CONNECTED) {
err = -ENOTCONN;
goto fail;
}
LOG_DBG("conn %p buf %p len %u flags 0x%02x", conn, buf, buf->len,
flags);
/* 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);
if (IS_ENABLED(CONFIG_BT_ISO) && conn->type == BT_CONN_TYPE_ISO) {
err = send_iso(conn, buf, flags);
} else if (IS_ENABLED(CONFIG_BT_CONN)) {
err = send_acl(conn, buf, flags);
} else {
__ASSERT(false, "Invalid connection type %u", conn->type);
}
if (err) {
LOG_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);
/* We don't want to end up in a situation where send_acl/iso
* returns the same error code as when we don't get a buffer in
* time.
*/
err = -EIO;
goto fail;
}
return 0;
fail:
/* If we get here, something has seriously gone wrong:
* We also need to destroy the `parent` buf.
*/
k_sem_give(bt_conn_get_pkts(conn));
if (tx) {
/* `buf` might not get destroyed, and its `tx` pointer will still be reachable.
* Make sure that we don't try to use the destroyed context later.
*/
tx_data(buf)->tx = NULL;
conn_tx_destroy(conn, tx);
}
return err;
}
static int send_frag(struct bt_conn *conn,
struct net_buf *buf, struct net_buf *frag,
uint8_t flags)
{
/* Check if the controller can accept ACL packets */
if (k_sem_take(bt_conn_get_pkts(conn), K_NO_WAIT)) {
LOG_DBG("no controller bufs");
return -ENOBUFS;
}
/* Add the data to the buffer */
if (frag) {
uint16_t 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);
} else {
/* De-queue the buffer now that we know we can send it.
* Only applies if the buffer to be sent is the original buffer,
* and not one of its fragments.
* This buffer was fetched from the FIFO using a peek operation.
*/
buf = net_buf_get(&conn->tx_queue, K_NO_WAIT);
frag = buf;
}
return do_send_frag(conn, frag, flags);
}
static struct net_buf *create_frag(struct bt_conn *conn, struct net_buf *buf)
{
struct net_buf *frag;
switch (conn->type) {
#if defined(CONFIG_BT_ISO)
case BT_CONN_TYPE_ISO:
frag = bt_iso_create_frag(0);
break;
#endif
default:
#if defined(CONFIG_BT_CONN)
frag = bt_conn_create_frag(0);
#else
return NULL;
#endif /* CONFIG_BT_CONN */
}
if (conn->state != BT_CONN_CONNECTED) {
net_buf_unref(frag);
return NULL;
}
/* Fragments never have a TX completion callback */
tx_data(frag)->tx = NULL;
tx_data(frag)->is_cont = false;
tx_data(frag)->iso_has_ts = tx_data(buf)->iso_has_ts;
return frag;
}
static int send_buf(struct bt_conn *conn, struct net_buf *buf)
{
struct net_buf *frag;
uint8_t flags;
int err;
LOG_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) && !tx_data(buf)->is_cont) {
LOG_DBG("send single");
return send_frag(conn, buf, NULL, FRAG_SINGLE);
}
LOG_DBG("start fragmenting");
/*
* Send the fragments. For the last one simply use the original
* buffer (which works since we've used net_buf_pull on it).
*/
flags = FRAG_START;
if (tx_data(buf)->is_cont) {
flags = FRAG_CONT;
}
while (buf->len > conn_mtu(conn)) {
frag = create_frag(conn, buf);
if (!frag) {
return -ENOMEM;
}
err = send_frag(conn, buf, frag, flags);
if (err) {
LOG_DBG("%p failed, mark as existing frag", buf);
tx_data(buf)->is_cont = flags != FRAG_START;
net_buf_unref(frag);
return err;
}
flags = FRAG_CONT;
}
LOG_DBG("last frag");
tx_data(buf)->is_cont = true;
return send_frag(conn, buf, NULL, FRAG_END);
}
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))) {
struct bt_conn_tx *tx = tx_data(buf)->tx;
tx_data(buf)->tx = NULL;
/* destroy the buffer */
net_buf_unref(buf);
/* destroy the tx context (and any associated meta-data) */
if (tx) {
conn_tx_destroy(conn, tx);
}
}
__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_work_reschedule(&conn->deferred_work, K_NO_WAIT);
}
static void conn_destroy(struct bt_conn *conn, void *data)
{
if (conn->state == BT_CONN_CONNECTED ||
conn->state == BT_CONN_DISCONNECTING) {
bt_conn_set_state(conn, BT_CONN_DISCONNECT_COMPLETE);
}
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
}
void bt_conn_cleanup_all(void)
{
bt_conn_foreach(BT_CONN_TYPE_ALL, conn_destroy, NULL);
}
static int conn_prepare_events(struct bt_conn *conn,
struct k_poll_event *events)
{
if (!atomic_get(&conn->ref)) {
return -ENOTCONN;
}
if (conn->state == BT_CONN_DISCONNECTED &&
atomic_test_and_clear_bit(conn->flags, BT_CONN_CLEANUP)) {
conn_cleanup(conn);
return -ENOTCONN;
}
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
LOG_DBG("Adding conn %p to poll list", conn);
/* ISO Synchronized Receiver only builds do not transmit and hence
* may not have any tx buffers allocated in a Controller.
*/
struct k_sem *conn_pkts = bt_conn_get_pkts(conn);
if (!conn_pkts) {
return -ENOTCONN;
}
bool buffers_available = k_sem_count_get(conn_pkts) > 0;
bool packets_waiting = !k_fifo_is_empty(&conn->tx_queue);
if (packets_waiting && !buffers_available) {
/* Only resume sending when the controller has buffer space
* available for this connection.
*/
LOG_DBG("wait on ctlr buffers");
k_poll_event_init(&events[0],
K_POLL_TYPE_SEM_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
conn_pkts);
} else {
/* Wait until there is more data to send. */
LOG_DBG("wait on host fifo");
k_poll_event_init(&events[0],
K_POLL_TYPE_FIFO_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&conn->tx_queue);
}
events[0].tag = BT_EVENT_CONN_TX_QUEUE;
return 0;
}
int bt_conn_prepare_events(struct k_poll_event events[])
{
int i, ev_count = 0;
struct bt_conn *conn;
LOG_DBG("");
k_poll_signal_init(&conn_change);
k_poll_event_init(&events[ev_count++], K_POLL_TYPE_SIGNAL,
K_POLL_MODE_NOTIFY_ONLY, &conn_change);
#if defined(CONFIG_BT_CONN)
for (i = 0; i < ARRAY_SIZE(acl_conns); i++) {
conn = &acl_conns[i];
if (!conn_prepare_events(conn, &events[ev_count])) {
ev_count++;
}
}
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_ISO)
for (i = 0; i < ARRAY_SIZE(iso_conns); i++) {
conn = &iso_conns[i];
if (!conn_prepare_events(conn, &events[ev_count])) {
ev_count++;
}
}
#endif
return ev_count;
}
void bt_conn_process_tx(struct bt_conn *conn)
{
struct net_buf *buf;
int err;
LOG_DBG("conn %p", conn);
if (conn->state == BT_CONN_DISCONNECTED &&
atomic_test_and_clear_bit(conn->flags, BT_CONN_CLEANUP)) {
LOG_DBG("handle %u disconnected - cleaning up", conn->handle);
conn_cleanup(conn);
return;
}
/* Get next ACL packet for connection. The buffer will only get dequeued
* if there is a free controller buffer to put it in.
*
* Important: no operations should be done on `buf` until it is properly
* dequeued from the FIFO, using the `net_buf_get()` API.
*/
buf = k_fifo_peek_head(&conn->tx_queue);
BT_ASSERT(buf);
/* Since we used `peek`, the queue still owns the reference to the
* buffer, so we need to take an explicit additional reference here.
*/
buf = net_buf_ref(buf);
err = send_buf(conn, buf);
net_buf_unref(buf);
if (err == -EIO) {
struct bt_conn_tx *tx = tx_data(buf)->tx;
tx_data(buf)->tx = NULL;
/* destroy the buffer */
net_buf_unref(buf);
/* destroy the tx context (and any associated meta-data) */
if (tx) {
conn_tx_destroy(conn, tx);
}
}
}
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);
conn_tx_destroy(conn, tx);
k_sem_give(bt_conn_get_pkts(conn));
}
}
struct bt_conn *conn_lookup_handle(struct bt_conn *conns, size_t size,
uint16_t handle)
{
int i;
for (i = 0; i < size; i++) {
struct bt_conn *conn = bt_conn_ref(&conns[i]);
if (!conn) {
continue;
}
/* We only care about connections with a valid handle */
if (!bt_conn_is_handle_valid(conn)) {
bt_conn_unref(conn);
continue;
}
if (conn->handle != handle) {
bt_conn_unref(conn);
continue;
}
return conn;
}
return NULL;
}
void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
{
bt_conn_state_t old_state;
LOG_DBG("%s -> %s", state2str(conn->state), state2str(state));
if (conn->state == state) {
LOG_WRN("no transition %s", state2str(state));
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.
*/
if (conn->type != BT_CONN_TYPE_ISO) {
bt_conn_ref(conn);
}
break;
case BT_CONN_CONNECTING:
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
conn->type == BT_CONN_TYPE_LE) {
k_work_cancel_delayable(&conn->deferred_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);
k_poll_signal_raise(&conn_change, 0);
if (IS_ENABLED(CONFIG_BT_ISO) &&
conn->type == BT_CONN_TYPE_ISO) {
bt_iso_connected(conn);
break;
}
#if defined(CONFIG_BT_CONN)
sys_slist_init(&conn->channels);
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) &&
conn->role == BT_CONN_ROLE_PERIPHERAL) {
#if defined(CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS)
if (conn->type == BT_CONN_TYPE_LE) {
conn->le.conn_param_retry_countdown =
CONFIG_BT_CONN_PARAM_RETRY_COUNT;
}
#endif /* CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS */
k_work_schedule(&conn->deferred_work,
CONN_UPDATE_TIMEOUT);
}
#endif /* CONFIG_BT_CONN */
break;
case BT_CONN_DISCONNECTED:
#if defined(CONFIG_BT_CONN)
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.
*/
switch (old_state) {
case BT_CONN_DISCONNECT_COMPLETE:
tx_notify(conn);
/* Cancel Connection Update if it is pending */
if ((conn->type == BT_CONN_TYPE_LE) &&
(k_work_delayable_busy_get(&conn->deferred_work) &
(K_WORK_QUEUED | K_WORK_DELAYED))) {
k_work_cancel_delayable(&conn->deferred_work);
}
atomic_set_bit(conn->flags, BT_CONN_CLEANUP);
k_poll_signal_raise(&conn_change, 0);
/* The last ref will be dropped during cleanup */
break;
case BT_CONN_CONNECTING:
/* LE Create Connection command failed. This might be
* directly from the API, don't notify application in
* this case.
*/
if (conn->err) {
notify_connected(conn);
}
bt_conn_unref(conn);
break;
case BT_CONN_CONNECTING_SCAN:
/* this indicate LE Create Connection with peer address
* has been stopped. This could either be triggered by
* the application through bt_conn_disconnect or by
* timeout set by bt_conn_le_create_param.timeout.
*/
if (conn->err) {
notify_connected(conn);
}
bt_conn_unref(conn);
break;
case BT_CONN_CONNECTING_DIR_ADV:
/* this indicate Directed advertising stopped */
if (conn->err) {
notify_connected(conn);
}
bt_conn_unref(conn);
break;
case BT_CONN_CONNECTING_AUTO:
/* this indicates LE Create Connection with filter
* policy has been stopped. This can only be triggered
* by the application, so don't notify.
*/
bt_conn_unref(conn);
break;
case BT_CONN_CONNECTING_ADV:
/* This can only happen when application stops the
* advertiser, conn->err is never set in this case.
*/
bt_conn_unref(conn);
break;
case BT_CONN_CONNECTED:
case BT_CONN_DISCONNECTING:
case BT_CONN_DISCONNECTED:
/* Cannot happen. */
LOG_WRN("Invalid (%u) old state", state);
break;
}
break;
case BT_CONN_CONNECTING_AUTO:
break;
case BT_CONN_CONNECTING_ADV:
break;
case BT_CONN_CONNECTING_SCAN:
break;
case BT_CONN_CONNECTING_DIR_ADV:
break;
case BT_CONN_CONNECTING:
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 &&
bt_dev.create_param.timeout != 0) {
k_work_schedule(&conn->deferred_work,
K_MSEC(10 * bt_dev.create_param.timeout));
}
break;
case BT_CONN_DISCONNECTING:
break;
#endif /* CONFIG_BT_CONN */
case BT_CONN_DISCONNECT_COMPLETE:
process_unack_tx(conn);
break;
default:
LOG_WRN("no valid (%u) state was set", state);
break;
}
}
struct bt_conn *bt_conn_lookup_handle(uint16_t handle, enum bt_conn_type type)
{
struct bt_conn *conn;
#if defined(CONFIG_BT_CONN)
conn = conn_lookup_handle(acl_conns, ARRAY_SIZE(acl_conns), handle);
if (conn) {
goto found;
}
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_ISO)
conn = conn_lookup_handle(iso_conns, ARRAY_SIZE(iso_conns), handle);
if (conn) {
goto found;
}
#endif
#if defined(CONFIG_BT_BREDR)
conn = conn_lookup_handle(sco_conns, ARRAY_SIZE(sco_conns), handle);
if (conn) {
goto found;
}
#endif
found:
if (conn) {
if (type & conn->type) {
return conn;
}
LOG_WRN("incompatible handle %u", handle);
bt_conn_unref(conn);
}
return NULL;
}
void bt_conn_foreach(enum bt_conn_type type,
void (*func)(struct bt_conn *conn, void *data),
void *data)
{
int i;
#if defined(CONFIG_BT_CONN)
for (i = 0; i < ARRAY_SIZE(acl_conns); i++) {
struct bt_conn *conn = bt_conn_ref(&acl_conns[i]);
if (!conn) {
continue;
}
if (!(conn->type & type)) {
bt_conn_unref(conn);
continue;
}
func(conn, data);
bt_conn_unref(conn);
}
#if defined(CONFIG_BT_BREDR)
if (type & BT_CONN_TYPE_SCO) {
for (i = 0; i < ARRAY_SIZE(sco_conns); i++) {
struct bt_conn *conn = bt_conn_ref(&sco_conns[i]);
if (!conn) {
continue;
}
func(conn, data);
bt_conn_unref(conn);
}
}
#endif /* defined(CONFIG_BT_BREDR) */
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_ISO)
if (type & BT_CONN_TYPE_ISO) {
for (i = 0; i < ARRAY_SIZE(iso_conns); i++) {
struct bt_conn *conn = bt_conn_ref(&iso_conns[i]);
if (!conn) {
continue;
}
func(conn, data);
bt_conn_unref(conn);
}
}
#endif /* defined(CONFIG_BT_ISO) */
}
struct bt_conn *bt_conn_ref(struct bt_conn *conn)
{
atomic_val_t old;
__ASSERT_NO_MSG(conn);
/* Reference counter must be checked to avoid incrementing ref from
* zero, then we should return NULL instead.
* Loop on clear-and-set in case someone has modified the reference
* count since the read, and start over again when that happens.
*/
do {
old = atomic_get(&conn->ref);
if (!old) {
return NULL;
}
} while (!atomic_cas(&conn->ref, old, old + 1));
LOG_DBG("handle %u ref %ld -> %ld", conn->handle, old, old + 1);
return conn;
}
void bt_conn_unref(struct bt_conn *conn)
{
atomic_val_t old;
old = atomic_dec(&conn->ref);
LOG_DBG("handle %u ref %ld -> %ld", conn->handle, old, atomic_get(&conn->ref));
__ASSERT(old > 0, "Conn reference counter is 0");
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn->type == BT_CONN_TYPE_LE &&
conn->role == BT_CONN_ROLE_PERIPHERAL && atomic_get(&conn->ref) == 0) {
bt_le_adv_resume();
}
}
uint8_t bt_conn_index(const struct bt_conn *conn)
{
ptrdiff_t index = 0;
switch (conn->type) {
#if defined(CONFIG_BT_ISO)
case BT_CONN_TYPE_ISO:
index = conn - iso_conns;
__ASSERT(index >= 0 && index < ARRAY_SIZE(iso_conns),
"Invalid bt_conn pointer");
break;
#endif
#if defined(CONFIG_BT_BREDR)
case BT_CONN_TYPE_SCO:
index = conn - sco_conns;
__ASSERT(index >= 0 && index < ARRAY_SIZE(sco_conns),
"Invalid bt_conn pointer");
break;
#endif
default:
#if defined(CONFIG_BT_CONN)
index = conn - acl_conns;
__ASSERT(index >= 0 && index < ARRAY_SIZE(acl_conns),
"Invalid bt_conn pointer");
#else
__ASSERT(false, "Invalid connection type %u", conn->type);
#endif /* CONFIG_BT_CONN */
break;
}
return (uint8_t)index;
}
#if defined(CONFIG_NET_BUF_LOG)
struct net_buf *bt_conn_create_pdu_timeout_debug(struct net_buf_pool *pool,
size_t reserve,
k_timeout_t timeout,
const char *func, int line)
#else
struct net_buf *bt_conn_create_pdu_timeout(struct net_buf_pool *pool,
size_t reserve, k_timeout_t timeout)
#endif
{
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) {
#if defined(CONFIG_BT_CONN)
pool = &acl_tx_pool;
#else
return NULL;
#endif /* CONFIG_BT_CONN */
}
if (IS_ENABLED(CONFIG_BT_CONN_LOG_LEVEL_DBG)) {
#if defined(CONFIG_NET_BUF_LOG)
buf = net_buf_alloc_fixed_debug(pool, K_NO_WAIT, func, line);
#else
buf = net_buf_alloc(pool, K_NO_WAIT);
#endif
if (!buf) {
LOG_WRN("Unable to allocate buffer with K_NO_WAIT");
#if defined(CONFIG_NET_BUF_LOG)
buf = net_buf_alloc_fixed_debug(pool, timeout, func,
line);
#else
buf = net_buf_alloc(pool, timeout);
#endif
}
} else {
#if defined(CONFIG_NET_BUF_LOG)
buf = net_buf_alloc_fixed_debug(pool, timeout, func,
line);
#else
buf = net_buf_alloc(pool, timeout);
#endif
}
if (!buf) {
LOG_WRN("Unable to allocate buffer within timeout");
return NULL;
}
reserve += sizeof(struct bt_hci_acl_hdr) + BT_BUF_RESERVE;
net_buf_reserve(buf, reserve);
return buf;
}
#if defined(CONFIG_BT_CONN_TX)
static void tx_complete_work(struct k_work *work)
{
struct bt_conn *conn = CONTAINER_OF(work, struct bt_conn,
tx_complete_work);
LOG_DBG("conn %p", conn);
tx_notify(conn);
}
#endif /* CONFIG_BT_CONN_TX */
/* Group Connected BT_CONN only in this */
#if defined(CONFIG_BT_CONN)
void bt_conn_connected(struct bt_conn *conn)
{
bt_l2cap_connected(conn);
notify_connected(conn);
}
static int conn_disconnect(struct bt_conn *conn, uint8_t reason)
{
int err;
err = bt_hci_disconnect(conn->handle, reason);
if (err) {
return err;
}
if (conn->state == BT_CONN_CONNECTED) {
bt_conn_set_state(conn, BT_CONN_DISCONNECTING);
}
return 0;
}
int bt_conn_disconnect(struct bt_conn *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.
*/
#if !defined(CONFIG_BT_FILTER_ACCEPT_LIST)
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_FILTER_ACCEPT_LIST) */
switch (conn->state) {
case BT_CONN_CONNECTING_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_CONNECTING:
if (conn->type == BT_CONN_TYPE_LE) {
if (IS_ENABLED(CONFIG_BT_CENTRAL)) {
k_work_cancel_delayable(&conn->deferred_work);
return bt_le_create_conn_cancel();
}
}
#if defined(CONFIG_BT_ISO)
else if (conn->type == BT_CONN_TYPE_ISO) {
return conn_disconnect(conn, reason);
}
#endif /* CONFIG_BT_ISO */
#if defined(CONFIG_BT_BREDR)
else if (conn->type == BT_CONN_TYPE_BR) {
return bt_hci_connect_br_cancel(conn);
}
#endif /* CONFIG_BT_BREDR */
else {
__ASSERT(false, "Invalid conn type %u", conn->type);
}
return 0;
case BT_CONN_CONNECTED:
return conn_disconnect(conn, reason);
case BT_CONN_DISCONNECTING:
return 0;
case BT_CONN_DISCONNECTED:
default:
return -ENOTCONN;
}
}
static void notify_connected(struct bt_conn *conn)
{
for (struct bt_conn_cb *cb = callback_list; cb; cb = cb->_next) {
if (cb->connected) {
cb->connected(conn, conn->err);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb) {
if (cb->connected) {
cb->connected(conn, conn->err);
}
}
}
static void notify_disconnected(struct bt_conn *conn)
{
for (struct bt_conn_cb *cb = callback_list; cb; cb = cb->_next) {
if (cb->disconnected) {
cb->disconnected(conn, conn->err);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb) {
if (cb->disconnected) {
cb->disconnected(conn, conn->err);
}
}
}
#if defined(CONFIG_BT_REMOTE_INFO)
void notify_remote_info(struct bt_conn *conn)
{
struct bt_conn_remote_info remote_info;
int err;
err = bt_conn_get_remote_info(conn, &remote_info);
if (err) {
LOG_DBG("Notify remote info failed %d", err);
return;
}
for (struct bt_conn_cb *cb = callback_list; cb; cb = cb->_next) {
if (cb->remote_info_available) {
cb->remote_info_available(conn, &remote_info);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb) {
if (cb->remote_info_available) {
cb->remote_info_available(conn, &remote_info);
}
}
}
#endif /* defined(CONFIG_BT_REMOTE_INFO) */
void notify_le_param_updated(struct bt_conn *conn)
{
/* If new connection parameters meet requirement of pending
* parameters don't send peripheral conn param request anymore on timeout
*/
if (atomic_test_bit(conn->flags, BT_CONN_PERIPHERAL_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_PERIPHERAL_PARAM_SET);
}
for (struct bt_conn_cb *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);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb) {
if (cb->le_param_updated) {
cb->le_param_updated(conn, conn->le.interval,
conn->le.latency,
conn->le.timeout);
}
}
}
#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE)
void notify_le_data_len_updated(struct bt_conn *conn)
{
for (struct bt_conn_cb *cb = callback_list; cb; cb = cb->_next) {
if (cb->le_data_len_updated) {
cb->le_data_len_updated(conn, &conn->le.data_len);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb) {
if (cb->le_data_len_updated) {
cb->le_data_len_updated(conn, &conn->le.data_len);
}
}
}
#endif
#if defined(CONFIG_BT_USER_PHY_UPDATE)
void notify_le_phy_updated(struct bt_conn *conn)
{
for (struct bt_conn_cb *cb = callback_list; cb; cb = cb->_next) {
if (cb->le_phy_updated) {
cb->le_phy_updated(conn, &conn->le.phy);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb) {
if (cb->le_phy_updated) {
cb->le_phy_updated(conn, &conn->le.phy);
}
}
}
#endif
bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
{
if (!bt_le_conn_params_valid(param)) {
return false;
}
for (struct bt_conn_cb *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;
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb) {
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)
{
LOG_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);
/* Proceed only if connection parameters contains valid values*/
if (!bt_le_conn_params_valid(param)) {
return -EINVAL;
}
/* Use LE connection parameter request if both local and remote support
* it; or if local role is central 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_PERIPHERAL_PARAM_L2CAP)) ||
(conn->role == BT_HCI_ROLE_CENTRAL)) {
int rc;
rc = bt_conn_le_conn_update(conn, param);
/* store those in case of fallback to L2CAP */
if (rc == 0) {
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;
}
return rc;
}
/* If remote central does not support LL Connection Parameters Request
* Procedure
*/
return bt_l2cap_update_conn_param(conn, param);
}
#if defined(CONFIG_BT_ISO_UNICAST)
static struct bt_conn *conn_lookup_iso(struct bt_conn *conn)
{
int i;
for (i = 0; i < ARRAY_SIZE(iso_conns); i++) {
struct bt_conn *iso = bt_conn_ref(&iso_conns[i]);
if (iso == NULL) {
continue;
}
if (iso->iso.acl == conn) {
return iso;
}
bt_conn_unref(iso);
}
return NULL;
}
#endif /* CONFIG_BT_ISO */
static void deferred_work(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct bt_conn *conn = CONTAINER_OF(dwork, struct bt_conn, deferred_work);
const struct bt_le_conn_param *param;
LOG_DBG("conn %p", conn);
if (conn->state == BT_CONN_DISCONNECTED) {
#if defined(CONFIG_BT_ISO_UNICAST)
struct bt_conn *iso;
if (conn->type == BT_CONN_TYPE_ISO) {
/* bt_iso_disconnected is responsible for unref'ing the
* connection pointer, as it is conditional on whether
* the connection is a central or peripheral.
*/
bt_iso_disconnected(conn);
return;
}
/* Mark all ISO channels associated
* with ACL conn as not connected, and
* remove ACL reference
*/
iso = conn_lookup_iso(conn);
while (iso != NULL) {
struct bt_iso_chan *chan = iso->iso.chan;
if (chan != NULL) {
bt_iso_chan_set_state(chan,
BT_ISO_STATE_DISCONNECTING);
}
bt_iso_cleanup_acl(iso);
bt_conn_unref(iso);
iso = conn_lookup_iso(conn);
}
#endif
bt_l2cap_disconnected(conn);
notify_disconnected(conn);
/* 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_CENTRAL) {
/* 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_le_create_conn_cancel();
return;
}
/* if application set own params use those, otherwise use defaults. */
if (atomic_test_and_clear_bit(conn->flags,
BT_CONN_PERIPHERAL_PARAM_SET)) {
int err;
param = BT_LE_CONN_PARAM(conn->le.interval_min,
conn->le.interval_max,
conn->le.pending_latency,
conn->le.pending_timeout);
err = send_conn_le_param_update(conn, param);
if (!err) {
atomic_clear_bit(conn->flags,
BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE);
} else {
LOG_WRN("Send LE param update failed (err %d)", err);
}
} else if (IS_ENABLED(CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS)) {
#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS)
int err;
param = BT_LE_CONN_PARAM(
CONFIG_BT_PERIPHERAL_PREF_MIN_INT,
CONFIG_BT_PERIPHERAL_PREF_MAX_INT,
CONFIG_BT_PERIPHERAL_PREF_LATENCY,
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT);
err = send_conn_le_param_update(conn, param);
if (!err) {
atomic_set_bit(conn->flags,
BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE);
} else {
LOG_WRN("Send auto LE param update failed (err %d)",
err);
}
#endif
}
atomic_set_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_UPDATE);
}
static struct bt_conn *acl_conn_new(void)
{
return bt_conn_new(acl_conns, ARRAY_SIZE(acl_conns));
}
#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)
{
return bt_conn_new(sco_conns, ARRAY_SIZE(sco_conns));
}
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_CONNECTING:
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_CONNECTING);
conn->role = BT_CONN_ROLE_CENTRAL;
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_CONNECTING:
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));
LOG_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_CONNECTING);
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++) {
struct bt_conn *conn = bt_conn_ref(&sco_conns[i]);
if (!conn) {
continue;
}
if (conn->type != BT_CONN_TYPE_SCO) {
bt_conn_unref(conn);
continue;
}
if (!bt_addr_eq(peer, &conn->sco.acl->br.dst)) {
bt_conn_unref(conn);
continue;
}
return conn;
}
return NULL;
}
struct bt_conn *bt_conn_lookup_addr_br(const bt_addr_t *peer)
{
int i;
for (i = 0; i < ARRAY_SIZE(acl_conns); i++) {
struct bt_conn *conn = bt_conn_ref(&acl_conns[i]);
if (!conn) {
continue;
}
if (conn->type != BT_CONN_TYPE_BR) {
bt_conn_unref(conn);
continue;
}
if (!bt_addr_eq(peer, &conn->br.dst)) {
bt_conn_unref(conn);
continue;
}
return conn;
}
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);
if (!sco_conn->sco.acl) {
bt_conn_unref(sco_conn);
return NULL;
}
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 = acl_conn_new();
if (!conn) {
return NULL;
}
bt_addr_copy(&conn->br.dst, peer);
conn->type = BT_CONN_TYPE_BR;
return conn;
}
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;
}
#endif /* CONFIG_BT_BREDR */
#if defined(CONFIG_BT_SMP)
bool bt_conn_ltk_present(const struct bt_conn *conn)
{
const struct bt_keys *keys = conn->le.keys;
if (!keys) {
keys = bt_keys_find_addr(conn->id, &conn->le.dst);
}
if (keys) {
if (conn->role == BT_HCI_ROLE_CENTRAL) {
return keys->keys & (BT_KEYS_LTK_P256 | BT_KEYS_PERIPH_LTK);
} else {
return keys->keys & (BT_KEYS_LTK_P256 | BT_KEYS_LTK);
}
}
return false;
}
void bt_conn_identity_resolved(struct bt_conn *conn)
{
const bt_addr_le_t *rpa;
if (conn->role == BT_HCI_ROLE_CENTRAL) {
rpa = &conn->le.resp_addr;
} else {
rpa = &conn->le.init_addr;
}
for (struct bt_conn_cb *cb = callback_list; cb; cb = cb->_next) {
if (cb->identity_resolved) {
cb->identity_resolved(conn, rpa, &conn->le.dst);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb) {
if (cb->identity_resolved) {
cb->identity_resolved(conn, rpa, &conn->le.dst);
}
}
}
int bt_conn_le_start_encryption(struct bt_conn *conn, uint8_t rand[8],
uint8_t ediv[2], const uint8_t *ltk, size_t len)
{
struct bt_hci_cp_le_start_encryption *cp;
struct net_buf *buf;
if (len > sizeof(cp->ltk)) {
return -EINVAL;
}
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)
uint8_t bt_conn_enc_key_size(const struct bt_conn *conn)
{
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;
uint8_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;
}
static void reset_pairing(struct bt_conn *conn)
{
#if defined(CONFIG_BT_BREDR)
if (conn->type == BT_CONN_TYPE_BR) {
atomic_clear_bit(conn->flags, BT_CONN_BR_PAIRING);
atomic_clear_bit(conn->flags, BT_CONN_BR_PAIRING_INITIATOR);
atomic_clear_bit(conn->flags, BT_CONN_BR_LEGACY_SECURE);
}
#endif /* CONFIG_BT_BREDR */
/* Reset required security level to current operational */
conn->required_sec_level = conn->sec_level;
}
void bt_conn_security_changed(struct bt_conn *conn, uint8_t hci_err,
enum bt_security_err err)
{
reset_pairing(conn);
bt_l2cap_security_changed(conn, hci_err);
if (IS_ENABLED(CONFIG_BT_ISO_CENTRAL)) {
bt_iso_security_changed(conn, hci_err);
}
for (struct bt_conn_cb *cb = callback_list; cb; cb = cb->_next) {
if (cb->security_changed) {
cb->security_changed(conn, conn->sec_level, err);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb) {
if (cb->security_changed) {
cb->security_changed(conn, conn->sec_level, err);
}
}
#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST)
if (!err && conn->sec_level >= BT_SECURITY_L2) {
if (conn->type == BT_CONN_TYPE_LE) {
bt_keys_update_usage(conn->id, bt_conn_get_dst(conn));
}
#if defined(CONFIG_BT_BREDR)
if (conn->type == BT_CONN_TYPE_BR) {
bt_keys_link_key_update_usage(&conn->br.dst);
}
#endif /* CONFIG_BT_BREDR */
}
#endif
}
static int start_security(struct bt_conn *conn)
{
if (IS_ENABLED(CONFIG_BT_BREDR) && conn->type == BT_CONN_TYPE_BR) {
return bt_ssp_start_security(conn);
}
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)
{
bool force_pair;
int err;
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
force_pair = sec & BT_SECURITY_FORCE_PAIR;
sec &= ~BT_SECURITY_FORCE_PAIR;
if (IS_ENABLED(CONFIG_BT_SMP_SC_ONLY)) {
sec = BT_SECURITY_L4;
}
if (IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY)) {
sec = BT_SECURITY_L3;
}
/* nothing to do */
if (!force_pair && (conn->sec_level >= sec || conn->required_sec_level >= sec)) {
return 0;
}
atomic_set_bit_to(conn->flags, BT_CONN_FORCE_PAIR, force_pair);
conn->required_sec_level = sec;
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(const struct bt_conn *conn)
{
return conn->sec_level;
}
#else
bt_security_t bt_conn_get_security(const 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;
}
bool bt_conn_exists_le(uint8_t id, const bt_addr_le_t *peer)
{
struct bt_conn *conn = bt_conn_lookup_addr_le(id, peer);
if (conn) {
/* Connection object already exists.
* If the connection state is not "disconnected",then the
* connection was created but has not yet been disconnected.
* If the connection state is "disconnected" then the connection
* still has valid references. The last reference of the stack
* is released after the disconnected callback.
*/
LOG_WRN("Found valid connection (%p) with address %s in %s state ", conn,
bt_addr_le_str(peer), state2str(conn->state));
bt_conn_unref(conn);
return true;
}
return false;
}
struct bt_conn *bt_conn_add_le(uint8_t id, const bt_addr_le_t *peer)
{
struct bt_conn *conn = acl_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;
}
bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, uint8_t id,
const bt_addr_le_t *peer)
{
if (id != conn->id) {
return false;
}
/* Check against conn dst address as it may be the identity address */
if (bt_addr_le_eq(peer, &conn->le.dst)) {
return true;
}
/* Check against initial connection address */
if (conn->role == BT_HCI_ROLE_CENTRAL) {
return bt_addr_le_eq(peer, &conn->le.resp_addr);
}
return bt_addr_le_eq(peer, &conn->le.init_addr);
}
struct bt_conn *bt_conn_lookup_addr_le(uint8_t id, const bt_addr_le_t *peer)
{
int i;
for (i = 0; i < ARRAY_SIZE(acl_conns); i++) {
struct bt_conn *conn = bt_conn_ref(&acl_conns[i]);
if (!conn) {
continue;
}
if (conn->type != BT_CONN_TYPE_LE) {
bt_conn_unref(conn);
continue;
}
if (!bt_conn_is_peer_addr_le(conn, id, peer)) {
bt_conn_unref(conn);
continue;
}
return conn;
}
return NULL;
}
struct bt_conn *bt_conn_lookup_state_le(uint8_t id, const bt_addr_le_t *peer,
const bt_conn_state_t state)
{
int i;
for (i = 0; i < ARRAY_SIZE(acl_conns); i++) {
struct bt_conn *conn = bt_conn_ref(&acl_conns[i]);
if (!conn) {
continue;
}
if (conn->type != BT_CONN_TYPE_LE) {
bt_conn_unref(conn);
continue;
}
if (peer && !bt_conn_is_peer_addr_le(conn, id, peer)) {
bt_conn_unref(conn);
continue;
}
if (!(conn->state == state && conn->id == id)) {
bt_conn_unref(conn);
continue;
}
return conn;
}
return NULL;
}
const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn)
{
return &conn->le.dst;
}
static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state)
{
switch (state) {
case BT_CONN_DISCONNECTED:
case BT_CONN_DISCONNECT_COMPLETE:
return BT_CONN_STATE_DISCONNECTED;
case BT_CONN_CONNECTING_SCAN:
case BT_CONN_CONNECTING_AUTO:
case BT_CONN_CONNECTING_ADV:
case BT_CONN_CONNECTING_DIR_ADV:
case BT_CONN_CONNECTING:
return BT_CONN_STATE_CONNECTING;
case BT_CONN_CONNECTED:
return BT_CONN_STATE_CONNECTED;
case BT_CONN_DISCONNECTING:
return BT_CONN_STATE_DISCONNECTING;
default:
__ASSERT(false, "Invalid conn state %u", state);
return 0;
}
}
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;
info->state = conn_internal_to_public_state(conn->state);
info->security.flags = 0;
info->security.level = bt_conn_get_security(conn);
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
info->security.enc_key_size = bt_conn_enc_key_size(conn);
#else
info->security.enc_key_size = 0;
#endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR */
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_CENTRAL) {
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;
#if defined(CONFIG_BT_USER_PHY_UPDATE)
info->le.phy = &conn->le.phy;
#endif
#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE)
info->le.data_len = &conn->le.data_len;
#endif
if (conn->le.keys && (conn->le.keys->flags & BT_KEYS_SC)) {
info->security.flags |= BT_SECURITY_FLAG_SC;
}
if (conn->le.keys && (conn->le.keys->flags & BT_KEYS_OOB)) {
info->security.flags |= BT_SECURITY_FLAG_OOB;
}
return 0;
#if defined(CONFIG_BT_BREDR)
case BT_CONN_TYPE_BR:
info->br.dst = &conn->br.dst;
return 0;
#endif
#if defined(CONFIG_BT_ISO)
case BT_CONN_TYPE_ISO:
if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) &&
conn->iso.info.type == BT_ISO_CHAN_TYPE_CONNECTED && conn->iso.acl != NULL) {
info->le.dst = &conn->iso.acl->le.dst;
info->le.src = &bt_dev.id_addr[conn->iso.acl->id];
} else {
info->le.src = BT_ADDR_LE_NONE;
info->le.dst = BT_ADDR_LE_NONE;
}
return 0;
#endif
default:
break;
}
return -EINVAL;
}
int bt_conn_get_remote_info(struct bt_conn *conn,
struct bt_conn_remote_info *remote_info)
{
if (!atomic_test_bit(conn->flags, BT_CONN_AUTO_FEATURE_EXCH) ||
(IS_ENABLED(CONFIG_BT_REMOTE_VERSION) &&
!atomic_test_bit(conn->flags, BT_CONN_AUTO_VERSION_INFO))) {
return -EBUSY;
}
remote_info->type = conn->type;
#if defined(CONFIG_BT_REMOTE_VERSION)
/* The conn->rv values will be just zeroes if the operation failed */
remote_info->version = conn->rv.version;
remote_info->manufacturer = conn->rv.manufacturer;
remote_info->subversion = conn->rv.subversion;
#else
remote_info->version = 0;
remote_info->manufacturer = 0;
remote_info->subversion = 0;
#endif
switch (conn->type) {
case BT_CONN_TYPE_LE:
remote_info->le.features = conn->le.features;
return 0;
#if defined(CONFIG_BT_BREDR)
case BT_CONN_TYPE_BR:
/* TODO: Make sure the HCI commands to read br features and
* extended features has finished. */
return -ENOTSUP;
#endif
default:
return -EINVAL;
}
}
/* Read Transmit Power Level HCI command */
static int bt_conn_get_tx_power_level(struct bt_conn *conn, uint8_t type,
int8_t *tx_power_level)
{
int err;
struct bt_hci_rp_read_tx_power_level *rp;
struct net_buf *rsp;
struct bt_hci_cp_read_tx_power_level *cp;
struct net_buf *buf;
buf = bt_hci_cmd_create(BT_HCI_OP_READ_TX_POWER_LEVEL, sizeof(*cp));
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->type = type;
cp->handle = sys_cpu_to_le16(conn->handle);
err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_TX_POWER_LEVEL, buf, &rsp);
if (err) {
return err;
}
rp = (void *) rsp->data;
*tx_power_level = rp->tx_power_level;
net_buf_unref(rsp);
return 0;
}
int bt_conn_le_get_tx_power_level(struct bt_conn *conn,
struct bt_conn_le_tx_power *tx_power_level)
{
int err;
if (tx_power_level->phy != 0) {
/* Extend the implementation when LE Enhanced Read Transmit
* Power Level HCI command is available for use.
*/
return -ENOTSUP;
}
err = bt_conn_get_tx_power_level(conn, BT_TX_POWER_LEVEL_CURRENT,
&tx_power_level->current_level);
if (err) {
return err;
}
err = bt_conn_get_tx_power_level(conn, BT_TX_POWER_LEVEL_MAX,
&tx_power_level->max_level);
return err;
}
int bt_conn_le_param_update(struct bt_conn *conn,
const struct bt_le_conn_param *param)
{
LOG_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_PERIPHERAL_PARAM_SET);
return -EALREADY;
}
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
conn->role == BT_CONN_ROLE_CENTRAL) {
return send_conn_le_param_update(conn, param);
}
if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) {
/* if peripheral conn param update timer expired just send request */
if (atomic_test_bit(conn->flags, BT_CONN_PERIPHERAL_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_PERIPHERAL_PARAM_SET);
}
return 0;
}
#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE)
int bt_conn_le_data_len_update(struct bt_conn *conn,
const struct bt_conn_le_data_len_param *param)
{
if (conn->le.data_len.tx_max_len == param->tx_max_len &&
conn->le.data_len.tx_max_time == param->tx_max_time) {
return -EALREADY;
}
return bt_le_set_data_len(conn, param->tx_max_len, param->tx_max_time);
}
#endif /* CONFIG_BT_USER_DATA_LEN_UPDATE */
#if defined(CONFIG_BT_USER_PHY_UPDATE)
int bt_conn_le_phy_update(struct bt_conn *conn,
const struct bt_conn_le_phy_param *param)
{
uint8_t phy_opts, all_phys;
if ((param->options & BT_CONN_LE_PHY_OPT_CODED_S2) &&
(param->options & BT_CONN_LE_PHY_OPT_CODED_S8)) {
phy_opts = BT_HCI_LE_PHY_CODED_ANY;
} else if (param->options & BT_CONN_LE_PHY_OPT_CODED_S2) {
phy_opts = BT_HCI_LE_PHY_CODED_S2;
} else if (param->options & BT_CONN_LE_PHY_OPT_CODED_S8) {
phy_opts = BT_HCI_LE_PHY_CODED_S8;
} else {
phy_opts = BT_HCI_LE_PHY_CODED_ANY;
}
all_phys = 0U;
if (param->pref_tx_phy == BT_GAP_LE_PHY_NONE) {
all_phys |= BT_HCI_LE_PHY_TX_ANY;
}
if (param->pref_rx_phy == BT_GAP_LE_PHY_NONE) {
all_phys |= BT_HCI_LE_PHY_RX_ANY;
}
return bt_le_set_phy(conn, all_phys, param->pref_tx_phy,
param->pref_rx_phy, phy_opts);
}
#endif
#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;
}
static bool create_param_validate(const struct bt_conn_le_create_param *param)
{
#if defined(CONFIG_BT_PRIVACY)
/* Initiation timeout cannot be greater than the RPA timeout */
const uint32_t timeout_max = (MSEC_PER_SEC / 10) * bt_dev.rpa_timeout;
if (param->timeout > timeout_max) {
return false;
}
#endif
return true;
}
static void create_param_setup(const struct bt_conn_le_create_param *param)
{
bt_dev.create_param = *param;
bt_dev.create_param.timeout =
(bt_dev.create_param.timeout != 0) ?
bt_dev.create_param.timeout :
(MSEC_PER_SEC / 10) * CONFIG_BT_CREATE_CONN_TIMEOUT;
bt_dev.create_param.interval_coded =
(bt_dev.create_param.interval_coded != 0) ?
bt_dev.create_param.interval_coded :
bt_dev.create_param.interval;
bt_dev.create_param.window_coded =
(bt_dev.create_param.window_coded != 0) ?
bt_dev.create_param.window_coded :
bt_dev.create_param.window;
}
#if defined(CONFIG_BT_FILTER_ACCEPT_LIST)
int bt_conn_le_create_auto(const struct bt_conn_le_create_param *create_param,
const struct bt_le_conn_param *param)
{
struct bt_conn *conn;
int err;
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
return -EAGAIN;
}
if (!bt_le_conn_params_valid(param)) {
return -EINVAL;
}
conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, BT_ADDR_LE_NONE,
BT_CONN_CONNECTING_AUTO);
if (conn) {
bt_conn_unref(conn);
return -EALREADY;
}
/* Scanning either to connect or explicit scan, either case scanner was
* started by application and should not be stopped.
*/
if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) {
return -EINVAL;
}
if (atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) {
return -EINVAL;
}
if (!bt_id_scan_random_addr_check()) {
return -EINVAL;
}
conn = bt_conn_add_le(BT_ID_DEFAULT, BT_ADDR_LE_NONE);
if (!conn) {
return -ENOMEM;
}
bt_conn_set_param_le(conn, param);
create_param_setup(create_param);
atomic_set_bit(conn->flags, BT_CONN_AUTO_CONNECT);
bt_conn_set_state(conn, BT_CONN_CONNECTING_AUTO);
err = bt_le_create_conn(conn);
if (err) {
LOG_ERR("Failed to start filtered scan");
conn->err = 0;
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
bt_conn_unref(conn);
return err;
}
/* Since we don't give the application a reference to manage in
* this case, we need to release this reference here.
*/
bt_conn_unref(conn);
return 0;
}
int bt_conn_create_auto_stop(void)
{
struct bt_conn *conn;
int err;
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
return -EINVAL;
}
conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, BT_ADDR_LE_NONE,
BT_CONN_CONNECTING_AUTO);
if (!conn) {
return -EINVAL;
}
if (!atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) {
return -EINVAL;
}
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
bt_conn_unref(conn);
err = bt_le_create_conn_cancel();
if (err) {
LOG_ERR("Failed to stop initiator");
return err;
}
return 0;
}
#endif /* defined(CONFIG_BT_FILTER_ACCEPT_LIST) */
static int conn_le_create_common_checks(const bt_addr_le_t *peer,
const struct bt_le_conn_param *conn_param)
{
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
return -EAGAIN;
}
if (!bt_le_conn_params_valid(conn_param)) {
return -EINVAL;
}
if (atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) {
return -EAGAIN;
}
if (atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) {
return -EALREADY;
}
if (!bt_id_scan_random_addr_check()) {
return -EINVAL;
}
if (bt_conn_exists_le(BT_ID_DEFAULT, peer)) {
return -EINVAL;
}
return 0;
}
static struct bt_conn *conn_le_create_helper(const bt_addr_le_t *peer,
const struct bt_le_conn_param *conn_param)
{
bt_addr_le_t dst;
struct bt_conn *conn;
if (bt_addr_le_is_resolved(peer)) {
bt_addr_le_copy_resolved(&dst, peer);
} 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;
}
bt_conn_set_param_le(conn, conn_param);
return conn;
}
int bt_conn_le_create(const bt_addr_le_t *peer, const struct bt_conn_le_create_param *create_param,
const struct bt_le_conn_param *conn_param, struct bt_conn **ret_conn)
{
struct bt_conn *conn;
int err;
err = conn_le_create_common_checks(peer, conn_param);
if (err) {
return err;
}
if (!create_param_validate(create_param)) {
return -EINVAL;
}
conn = conn_le_create_helper(peer, conn_param);
if (!conn) {
return -ENOMEM;
}
create_param_setup(create_param);
#if defined(CONFIG_BT_SMP)
if (bt_dev.le.rl_entries > bt_dev.le.rl_size) {
/* Use host-based identity resolving. */
bt_conn_set_state(conn, BT_CONN_CONNECTING_SCAN);
err = bt_le_scan_update(true);
if (err) {
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
bt_conn_unref(conn);
return err;
}
*ret_conn = conn;
return 0;
}
#endif
bt_conn_set_state(conn, BT_CONN_CONNECTING);
err = bt_le_create_conn(conn);
if (err) {
conn->err = 0;
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
bt_conn_unref(conn);
bt_le_scan_update(false);
return err;
}
*ret_conn = conn;
return 0;
}
int bt_conn_le_create_synced(const struct bt_le_ext_adv *adv,
const struct bt_conn_le_create_synced_param *synced_param,
const struct bt_le_conn_param *conn_param, struct bt_conn **ret_conn)
{
struct bt_conn *conn;
int err;
err = conn_le_create_common_checks(synced_param->peer, conn_param);
if (err) {
return err;
}
if (!atomic_test_bit(adv->flags, BT_PER_ADV_ENABLED)) {
return -EINVAL;
}
if (!BT_FEAT_LE_PAWR_ADVERTISER(bt_dev.le.features)) {
return -ENOTSUP;
}
if (synced_param->subevent >= BT_HCI_PAWR_SUBEVENT_MAX) {
return -EINVAL;
}
conn = conn_le_create_helper(synced_param->peer, conn_param);
if (!conn) {
return -ENOMEM;
}
/* The connection creation timeout is not really useful for PAwR.
* The controller will give a result for the connection attempt
* within a periodic interval. We do not know the periodic interval
* used, so disable the timeout.
*/
bt_dev.create_param.timeout = 0;
bt_conn_set_state(conn, BT_CONN_CONNECTING);
err = bt_le_create_conn_synced(conn, adv, synced_param->subevent);
if (err) {
conn->err = 0;
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
bt_conn_unref(conn);
return err;
}
*ret_conn = conn;
return 0;
}
#if !defined(CONFIG_BT_FILTER_ACCEPT_LIST)
int bt_le_set_auto_conn(const bt_addr_le_t *addr,
const struct bt_le_conn_param *param)
{
struct bt_conn *conn;
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
return -EAGAIN;
}
if (param && !bt_le_conn_params_valid(param)) {
return -EINVAL;
}
if (!bt_id_scan_random_addr_check()) {
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_CONNECTING_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_CONNECTING_SCAN);
}
bt_le_scan_update(false);
}
bt_conn_unref(conn);
return 0;
}
#endif /* !defined(CONFIG_BT_FILTER_ACCEPT_LIST) */
#endif /* CONFIG_BT_CENTRAL */
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);
}
#if defined(CONFIG_NET_BUF_LOG)
struct net_buf *bt_conn_create_frag_timeout_debug(size_t reserve,
k_timeout_t timeout,
const char *func, int line)
#else
struct net_buf *bt_conn_create_frag_timeout(size_t reserve, k_timeout_t timeout)
#endif
{
struct net_buf_pool *pool = NULL;
#if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0
pool = &frag_pool;
#endif
#if defined(CONFIG_NET_BUF_LOG)
return bt_conn_create_pdu_timeout_debug(pool, reserve, timeout,
func, line);
#else
return bt_conn_create_pdu_timeout(pool, reserve, timeout);
#endif /* CONFIG_NET_BUF_LOG */
}
#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;
}
#if defined(CONFIG_BT_SMP)
int bt_conn_auth_cb_overlay(struct bt_conn *conn, const struct bt_conn_auth_cb *cb)
{
CHECKIF(conn == NULL) {
return -EINVAL;
}
/* The cancel callback must always be provided if the app provides
* interactive callbacks.
*/
if (cb && !cb->cancel &&
(cb->passkey_display || cb->passkey_entry || cb->passkey_confirm ||
cb->pairing_confirm)) {
return -EINVAL;
}
if (conn->type == BT_CONN_TYPE_LE) {
return bt_smp_auth_cb_overlay(conn, cb);
}
return -ENOTSUP;
}
#endif
int bt_conn_auth_info_cb_register(struct bt_conn_auth_info_cb *cb)
{
CHECKIF(cb == NULL) {
return -EINVAL;
}
sys_slist_append(&bt_auth_info_cbs, &cb->node);
return 0;
}
int bt_conn_auth_info_cb_unregister(struct bt_conn_auth_info_cb *cb)
{
CHECKIF(cb == NULL) {
return -EINVAL;
}
if (!sys_slist_find_and_remove(&bt_auth_info_cbs, &cb->node)) {
return -EALREADY;
}
return 0;
}
int bt_conn_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey)
{
if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) {
return bt_smp_auth_passkey_entry(conn, passkey);
}
if (IS_ENABLED(CONFIG_BT_BREDR) && conn->type == BT_CONN_TYPE_BR) {
if (!bt_auth) {
return -EINVAL;
}
return bt_ssp_auth_passkey_entry(conn, passkey);
}
return -EINVAL;
}
#if defined(CONFIG_BT_PASSKEY_KEYPRESS)
int bt_conn_auth_keypress_notify(struct bt_conn *conn,
enum bt_conn_auth_keypress type)
{
if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) {
return bt_smp_auth_keypress_notify(conn, type);
}
LOG_ERR("Not implemented for conn type %d", conn->type);
return -EINVAL;
}
#endif
int bt_conn_auth_passkey_confirm(struct bt_conn *conn)
{
if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) {
return bt_smp_auth_passkey_confirm(conn);
}
if (IS_ENABLED(CONFIG_BT_BREDR) && conn->type == BT_CONN_TYPE_BR) {
if (!bt_auth) {
return -EINVAL;
}
return bt_ssp_auth_passkey_confirm(conn);
}
return -EINVAL;
}
int bt_conn_auth_cancel(struct bt_conn *conn)
{
if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) {
return bt_smp_auth_cancel(conn);
}
if (IS_ENABLED(CONFIG_BT_BREDR) && conn->type == BT_CONN_TYPE_BR) {
if (!bt_auth) {
return -EINVAL;
}
return bt_ssp_auth_cancel(conn);
}
return -EINVAL;
}
int bt_conn_auth_pairing_confirm(struct bt_conn *conn)
{
if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) {
return bt_smp_auth_pairing_confirm(conn);
}
if (IS_ENABLED(CONFIG_BT_BREDR) && conn->type == BT_CONN_TYPE_BR) {
if (!bt_auth) {
return -EINVAL;
}
return bt_ssp_auth_pairing_confirm(conn);
}
return -EINVAL;
}
#endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR */
struct bt_conn *bt_conn_lookup_index(uint8_t index)
{
if (index >= ARRAY_SIZE(acl_conns)) {
return NULL;
}
return bt_conn_ref(&acl_conns[index]);
}
int bt_conn_init(void)
{
int err, i;
k_fifo_init(&free_tx);
for (i = 0; i < ARRAY_SIZE(conn_tx); i++) {
k_fifo_put(&free_tx, &conn_tx[i]);
}
bt_att_init();
err = bt_smp_init();
if (err) {
return err;
}
bt_l2cap_init();
/* Initialize background scan */
if (IS_ENABLED(CONFIG_BT_CENTRAL)) {
for (i = 0; i < ARRAY_SIZE(acl_conns); i++) {
struct bt_conn *conn = bt_conn_ref(&acl_conns[i]);
if (!conn) {
continue;
}
#if !defined(CONFIG_BT_FILTER_ACCEPT_LIST)
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_CONNECTING_SCAN);
}
#endif /* !defined(CONFIG_BT_FILTER_ACCEPT_LIST) */
bt_conn_unref(conn);
}
}
return 0;
}
#if defined(CONFIG_BT_DF_CONNECTION_CTE_RX)
void bt_hci_le_df_connection_iq_report_common(uint8_t event, struct net_buf *buf)
{
struct bt_df_conn_iq_samples_report iq_report;
struct bt_conn *conn;
int err;
if (event == BT_HCI_EVT_LE_CONNECTION_IQ_REPORT) {
err = hci_df_prepare_connection_iq_report(buf, &iq_report, &conn);
if (err) {
LOG_ERR("Prepare CTE conn IQ report failed %d", err);
return;
}
} else if (IS_ENABLED(CONFIG_BT_DF_VS_CONN_IQ_REPORT_16_BITS_IQ_SAMPLES) &&
event == BT_HCI_EVT_VS_LE_CONNECTION_IQ_REPORT) {
err = hci_df_vs_prepare_connection_iq_report(buf, &iq_report, &conn);
if (err) {
LOG_ERR("Prepare CTE conn IQ report failed %d", err);
return;
}
} else {
LOG_ERR("Unhandled VS connection IQ report");
return;
}
for (struct bt_conn_cb *cb = callback_list; cb; cb = cb->_next) {
if (cb->cte_report_cb) {
cb->cte_report_cb(conn, &iq_report);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb)
{
if (cb->cte_report_cb) {
cb->cte_report_cb(conn, &iq_report);
}
}
bt_conn_unref(conn);
}
void bt_hci_le_df_connection_iq_report(struct net_buf *buf)
{
bt_hci_le_df_connection_iq_report_common(BT_HCI_EVT_LE_CONNECTION_IQ_REPORT, buf);
}
#if defined(CONFIG_BT_DF_VS_CONN_IQ_REPORT_16_BITS_IQ_SAMPLES)
void bt_hci_le_vs_df_connection_iq_report(struct net_buf *buf)
{
bt_hci_le_df_connection_iq_report_common(BT_HCI_EVT_VS_LE_CONNECTION_IQ_REPORT, buf);
}
#endif /* CONFIG_BT_DF_VS_CONN_IQ_REPORT_16_BITS_IQ_SAMPLES */
#endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */
#if defined(CONFIG_BT_DF_CONNECTION_CTE_REQ)
void bt_hci_le_df_cte_req_failed(struct net_buf *buf)
{
struct bt_df_conn_iq_samples_report iq_report;
struct bt_conn *conn;
int err;
err = hci_df_prepare_conn_cte_req_failed(buf, &iq_report, &conn);
if (err) {
LOG_ERR("Prepare CTE REQ failed IQ report failed %d", err);
return;
}
for (struct bt_conn_cb *cb = callback_list; cb; cb = cb->_next) {
if (cb->cte_report_cb) {
cb->cte_report_cb(conn, &iq_report);
}
}
STRUCT_SECTION_FOREACH(bt_conn_cb, cb)
{
if (cb->cte_report_cb) {
cb->cte_report_cb(conn, &iq_report);
}
}
bt_conn_unref(conn);
}
#endif /* CONFIG_BT_DF_CONNECTION_CTE_REQ */
#endif /* CONFIG_BT_CONN */