Bluetooth: Add support for tracking transmitted packets

Protocols/profiles may want to know when exactly their PDU has been
transmitted over the air. To make this possible, introduce support for
a callback that will get called when the controller reports that a
packet has been transmitted (through the Number of Completed Packets
HCI event).

Change-Id: Ia3a19b93c5b2111f144bfabe5861187c41525f30
Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
This commit is contained in:
Johan Hedberg 2017-03-17 08:02:57 +02:00 committed by Luiz Augusto von Dentz
commit 4be4c60ab6
9 changed files with 182 additions and 39 deletions

View file

@ -161,6 +161,15 @@ config BLUETOOTH_L2CAP_TX_MTU
help
Maximum L2CAP MTU for L2CAP TX buffers.
config BLUETOOTH_CONN_TX_MAX
int "Maximum number of pending TX buffers"
default 7
default BLUETOOTH_CONTROLLER_TX_BUFFERS if BLUETOOTH_CONTROLLER
range 1 128
help
Maximum number of pending TX buffers that have not yet
been acknowledged by the controller.
config BLUETOOTH_L2CAP_TX_USER_DATA_SIZE
int "Maximum supported user data size for L2CAP TX buffers"
default 4

View file

@ -13,6 +13,7 @@
#include <atomic.h>
#include <misc/byteorder.h>
#include <misc/util.h>
#include <misc/slist.h>
#include <misc/stack.h>
#include <misc/__assert.h>
@ -44,6 +45,16 @@ const struct bt_conn_auth_cb *bt_auth;
static struct bt_conn conns[CONFIG_BLUETOOTH_MAX_CONN];
static struct bt_conn_cb *callback_list;
struct conn_tx_cb {
bt_conn_tx_cb_t cb;
};
#define conn_tx(buf) ((struct conn_tx_cb *)net_buf_user_data(buf))
static struct bt_conn_tx conn_tx[CONFIG_BLUETOOTH_CONN_TX_MAX];
static sys_slist_t free_tx = SYS_SLIST_STATIC_INIT(&free_tx);
#if defined(CONFIG_BLUETOOTH_BREDR)
static struct bt_conn sco_conns[CONFIG_BLUETOOTH_MAX_SCO_CONN];
@ -1040,9 +1051,10 @@ void bt_conn_recv(struct bt_conn *conn, struct net_buf *buf, uint8_t flags)
bt_l2cap_recv(conn, buf);
}
int bt_conn_send(struct bt_conn *conn, struct net_buf *buf)
int bt_conn_send_cb(struct bt_conn *conn, struct net_buf *buf,
bt_conn_tx_cb_t cb)
{
BT_DBG("conn handle %u buf len %u", conn->handle, buf->len);
BT_DBG("conn handle %u buf len %u cb %p", conn->handle, buf->len, cb);
if (buf->pool->user_data_size < BT_BUF_USER_DATA_MIN) {
BT_ERR("Too small user data size");
@ -1056,14 +1068,55 @@ int bt_conn_send(struct bt_conn *conn, struct net_buf *buf)
return -ENOTCONN;
}
conn_tx(buf)->cb = cb;
net_buf_put(&conn->tx_queue, buf);
return 0;
}
static void tx_free(struct bt_conn_tx *tx)
{
tx->cb = NULL;
sys_slist_prepend(&free_tx, &tx->node);
}
void bt_conn_notify_tx(struct bt_conn *conn)
{
struct bt_conn_tx *tx;
BT_DBG("conn %p", conn);
while ((tx = k_fifo_get(&conn->tx_notify, K_NO_WAIT))) {
if (tx->cb) {
tx->cb(conn);
}
tx_free(tx);
}
}
static void add_pending_tx(struct bt_conn *conn, bt_conn_tx_cb_t cb)
{
sys_snode_t *node;
unsigned int key;
BT_DBG("conn %p cb %p", conn, cb);
__ASSERT(!sys_slist_is_empty(&free_tx), "No free conn TX contexts");
node = sys_slist_get_not_empty(&free_tx);
CONTAINER_OF(node, struct bt_conn_tx, node)->cb = cb;
key = irq_lock();
sys_slist_append(&conn->tx_pending, node);
irq_unlock(key);
}
static bool send_frag(struct bt_conn *conn, struct net_buf *buf, uint8_t flags,
bool always_consume)
{
struct bt_hci_acl_hdr *hdr;
bt_conn_tx_cb_t cb;
int err;
BT_DBG("conn %p buf %p len %u flags 0x%02x", conn, buf, buf->len,
@ -1072,6 +1125,9 @@ static bool send_frag(struct bt_conn *conn, struct net_buf *buf, uint8_t flags,
/* Wait until the controller can accept ACL packets */
k_sem_take(bt_conn_get_pkts(conn), K_FOREVER);
/* Make sure we notify and free up any pending tx contexts */
bt_conn_notify_tx(conn);
/* Check for disconnection while waiting for pkts_sem */
if (conn->state != BT_CONN_CONNECTED) {
goto fail;
@ -1081,6 +1137,7 @@ static bool send_frag(struct bt_conn *conn, struct net_buf *buf, uint8_t flags,
hdr->handle = sys_cpu_to_le16(bt_acl_handle_pack(conn->handle, flags));
hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr));
cb = conn_tx(buf)->cb;
bt_buf_set_type(buf, BT_BUF_ACL_OUT);
err = bt_send(buf);
@ -1089,7 +1146,7 @@ static bool send_frag(struct bt_conn *conn, struct net_buf *buf, uint8_t flags,
goto fail;
}
conn->pending_pkts++;
add_pending_tx(conn, cb);
return true;
fail:
@ -1123,6 +1180,9 @@ static struct net_buf *create_frag(struct bt_conn *conn, struct net_buf *buf)
return NULL;
}
/* Fragments never have a TX completion callback */
conn_tx(frag)->cb = NULL;
frag_len = min(conn_mtu(conn), net_buf_tailroom(frag));
net_buf_add_mem(frag, buf->data, frag_len);
@ -1181,7 +1241,7 @@ static void conn_cleanup(struct bt_conn *conn)
net_buf_unref(buf);
}
BT_ASSERT(!conn->pending_pkts);
__ASSERT(sys_slist_is_empty(&conn->tx_pending), "Pending TX packets");
bt_conn_reset_rx_state(conn);
@ -1224,7 +1284,13 @@ int bt_conn_prepare_events(struct k_poll_event events[])
K_POLL_TYPE_FIFO_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&conn->tx_queue);
events[ev_count++].tag = BT_EVENT_CONN_TX;
events[ev_count++].tag = BT_EVENT_CONN_TX_QUEUE;
k_poll_event_init(&events[ev_count],
K_POLL_TYPE_FIFO_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&conn->tx_notify);
events[ev_count++].tag = BT_EVENT_CONN_TX_NOTIFY;
}
return ev_count;
@ -1272,6 +1338,27 @@ struct bt_conn *bt_conn_add_le(const bt_addr_le_t *peer)
return conn;
}
static void process_unack_tx(struct bt_conn *conn)
{
/* Return any unacknowledged packets */
while (1) {
sys_snode_t *node;
unsigned int key;
key = irq_lock();
node = sys_slist_get(&conn->tx_pending);
irq_unlock(key);
if (!node) {
break;
}
tx_free(CONTAINER_OF(node, struct bt_conn_tx, node));
k_sem_give(bt_conn_get_pkts(conn));
}
}
void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
{
bt_conn_state_t old_state;
@ -1313,6 +1400,7 @@ void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
break;
}
k_fifo_init(&conn->tx_queue);
k_fifo_init(&conn->tx_notify);
k_poll_signal(&conn_change, 0);
sys_slist_init(&conn->channels);
@ -1334,12 +1422,7 @@ void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
old_state == BT_CONN_DISCONNECT) {
bt_l2cap_disconnected(conn);
notify_disconnected(conn);
/* Return any unacknowledged packets */
while (conn->pending_pkts) {
k_sem_give(bt_conn_get_pkts(conn));
conn->pending_pkts--;
}
process_unack_tx(conn);
/* Cancel Connection Update if it is pending */
if (conn->type == BT_CONN_TYPE_LE) {
@ -1944,7 +2027,11 @@ int bt_conn_auth_pairing_confirm(struct bt_conn *conn)
int bt_conn_init(void)
{
int err;
int err, i;
for (i = 0; i < ARRAY_SIZE(conn_tx); i++) {
sys_slist_prepend(&free_tx, &conn_tx[i].node);
}
bt_att_init();

View file

@ -72,6 +72,13 @@ struct bt_conn_sco {
};
#endif
typedef void (*bt_conn_tx_cb_t)(struct bt_conn *conn);
struct bt_conn_tx {
sys_snode_t node;
bt_conn_tx_cb_t cb;
};
struct bt_conn {
uint16_t handle;
uint8_t type;
@ -85,11 +92,14 @@ struct bt_conn {
uint8_t encrypt;
#endif /* CONFIG_BLUETOOTH_SMP || CONFIG_BLUETOOTH_BREDR */
uint8_t pending_pkts;
uint16_t rx_len;
struct net_buf *rx;
/* Sent but not acknowledged TX packets */
sys_slist_t tx_pending;
/* Acknowledged but not yet notified TX packets */
struct k_fifo tx_notify;
/* Queue for outgoing ACL data */
struct k_fifo tx_queue;
@ -116,7 +126,13 @@ struct bt_conn {
void bt_conn_recv(struct bt_conn *conn, struct net_buf *buf, uint8_t flags);
/* Send data over a connection */
int bt_conn_send(struct bt_conn *conn, struct net_buf *buf);
int bt_conn_send_cb(struct bt_conn *conn, struct net_buf *buf,
bt_conn_tx_cb_t cb);
static inline int bt_conn_send(struct bt_conn *conn, struct net_buf *buf)
{
return bt_conn_send_cb(conn, buf, NULL);
}
/* Add a new LE connection */
struct bt_conn *bt_conn_add_le(const bt_addr_le_t *peer);
@ -191,3 +207,4 @@ struct k_sem *bt_conn_get_pkts(struct bt_conn *conn);
/* k_poll related helpers for the TX thread */
int bt_conn_prepare_events(struct k_poll_event events[]);
void bt_conn_process_tx(struct bt_conn *conn);
void bt_conn_notify_tx(struct bt_conn *conn);

View file

@ -13,6 +13,7 @@
#include <errno.h>
#include <atomic.h>
#include <misc/util.h>
#include <misc/slist.h>
#include <misc/byteorder.h>
#include <misc/stack.h>
#include <misc/__assert.h>
@ -489,17 +490,21 @@ static void hci_num_completed_packets(struct net_buf *buf)
continue;
}
if (conn->pending_pkts >= count) {
conn->pending_pkts -= count;
} else {
BT_ERR("completed packets mismatch: %u > %u",
count, conn->pending_pkts);
conn->pending_pkts = 0;
}
irq_unlock(key);
while (count--) {
sys_snode_t *node;
key = irq_lock();
node = sys_slist_get(&conn->tx_pending);
irq_unlock(key);
if (!node) {
BT_ERR("packets count mismatch");
break;
}
k_fifo_put(&conn->tx_notify, node);
k_sem_give(bt_conn_get_pkts(conn));
}
@ -2727,14 +2732,21 @@ static void process_events(struct k_poll_event *ev, int count)
case K_POLL_STATE_FIFO_DATA_AVAILABLE:
if (ev->tag == BT_EVENT_CMD_TX) {
send_cmd();
} else if (IS_ENABLED(CONFIG_BLUETOOTH_CONN) &&
ev->tag == BT_EVENT_CONN_TX) {
} else if (IS_ENABLED(CONFIG_BLUETOOTH_CONN)) {
struct bt_conn *conn;
conn = CONTAINER_OF(ev->fifo, struct bt_conn,
if (ev->tag == BT_EVENT_CONN_TX_NOTIFY) {
conn = CONTAINER_OF(ev->fifo,
struct bt_conn,
tx_notify);
bt_conn_notify_tx(conn);
} else if (ev->tag == BT_EVENT_CONN_TX_QUEUE) {
conn = CONTAINER_OF(ev->fifo,
struct bt_conn,
tx_queue);
bt_conn_process_tx(conn);
}
}
break;
case K_POLL_STATE_NOT_READY:
break;
@ -2746,8 +2758,8 @@ static void process_events(struct k_poll_event *ev, int count)
}
#if defined(CONFIG_BLUETOOTH_CONN)
/* command FIFO + conn_change signal + MAX_CONN */
#define EV_COUNT (2 + CONFIG_BLUETOOTH_MAX_CONN)
/* command FIFO + conn_change signal + MAX_CONN * 2 (tx & tx_notify) */
#define EV_COUNT (2 + (CONFIG_BLUETOOTH_MAX_CONN * 2))
#else
/* command FIFO */
#define EV_COUNT 1
@ -2854,6 +2866,8 @@ static void read_buffer_size_complete(struct net_buf *buf)
BT_DBG("ACL BR/EDR buffers: pkts %u mtu %u", pkts, bt_dev.le.mtu);
pkts = min(pkts, CONFIG_BLUETOOTH_CONN_TX_MAX);
k_sem_init(&bt_dev.le.pkts, pkts, pkts);
}
#endif
@ -2862,16 +2876,19 @@ static void read_buffer_size_complete(struct net_buf *buf)
static void le_read_buffer_size_complete(struct net_buf *buf)
{
struct bt_hci_rp_le_read_buffer_size *rp = (void *)buf->data;
uint8_t le_max_num;
BT_DBG("status %u", rp->status);
bt_dev.le.mtu = sys_le16_to_cpu(rp->le_max_len);
if (bt_dev.le.mtu) {
k_sem_init(&bt_dev.le.pkts, rp->le_max_num, rp->le_max_num);
BT_DBG("ACL LE buffers: pkts %u mtu %u", rp->le_max_num,
bt_dev.le.mtu);
if (!bt_dev.le.mtu) {
return;
}
BT_DBG("ACL LE buffers: pkts %u mtu %u", rp->le_max_num, bt_dev.le.mtu);
le_max_num = min(rp->le_max_num, CONFIG_BLUETOOTH_CONN_TX_MAX);
k_sem_init(&bt_dev.le.pkts, le_max_num, le_max_num);
}
#endif

View file

@ -22,7 +22,8 @@
/* k_poll event tags */
enum {
BT_EVENT_CMD_TX,
BT_EVENT_CONN_TX,
BT_EVENT_CONN_TX_NOTIFY,
BT_EVENT_CONN_TX_QUEUE,
};
/* bt_dev flags: the flags defined here represent BT controller state */

View file

@ -18,6 +18,8 @@
#include <bluetooth/rfcomm.h>
#include <bluetooth/hfp_hf.h>
#include "hci_core.h"
#include "conn_internal.h"
#include "l2cap_internal.h"
#include "rfcomm_internal.h"
#include "at.h"

View file

@ -464,7 +464,8 @@ struct net_buf *bt_l2cap_create_pdu(struct net_buf_pool *pool, size_t reserve)
return bt_conn_create_pdu(pool, sizeof(struct bt_l2cap_hdr) + reserve);
}
void bt_l2cap_send(struct bt_conn *conn, uint16_t cid, struct net_buf *buf)
void bt_l2cap_send_cb(struct bt_conn *conn, uint16_t cid, struct net_buf *buf,
bt_conn_tx_cb_t cb)
{
struct bt_l2cap_hdr *hdr;
@ -474,7 +475,7 @@ void bt_l2cap_send(struct bt_conn *conn, uint16_t cid, struct net_buf *buf)
hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr));
hdr->cid = sys_cpu_to_le16(cid);
bt_conn_send(conn, buf);
bt_conn_send_cb(conn, buf, cb);
}
static void l2cap_send_reject(struct bt_conn *conn, uint8_t ident,

View file

@ -247,7 +247,14 @@ struct net_buf *bt_l2cap_create_pdu(struct net_buf_pool *pool, size_t reserve);
struct net_buf *bt_l2cap_create_rsp(struct net_buf *buf, size_t reserve);
/* Send L2CAP PDU over a connection */
void bt_l2cap_send(struct bt_conn *conn, uint16_t cid, struct net_buf *buf);
void bt_l2cap_send_cb(struct bt_conn *conn, uint16_t cid, struct net_buf *buf,
bt_conn_tx_cb_t cb);
static inline void bt_l2cap_send(struct bt_conn *conn, uint16_t cid,
struct net_buf *buf)
{
bt_l2cap_send_cb(conn, cid, buf, NULL);
}
/* Receive a new L2CAP PDU from a connection */
void bt_l2cap_recv(struct bt_conn *conn, struct net_buf *buf);

View file

@ -17,6 +17,8 @@
#include <bluetooth/log.h>
#include <bluetooth/sdp.h>
#include "hci_core.h"
#include "conn_internal.h"
#include "l2cap_internal.h"
#include "sdp_internal.h"