Bluetooth: Introduce flow control for outgoing ATT packets

In order to not overload the TX buffer pool and potentially run out of
them, enforce flow control for outgoing ATT packets so that the send
functions block until the PDU has actually been transmitted over the
air.

Change-Id: Ic065bb88aec8c2d0ac2def8ef62131a427f7051f
Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
This commit is contained in:
Johan Hedberg 2017-03-30 18:45:01 +03:00 committed by Luiz Augusto von Dentz
commit d893d31c16
2 changed files with 92 additions and 12 deletions

View file

@ -197,6 +197,16 @@ config BLUETOOTH_ATT_PREPARE_COUNT
Number of buffers available for ATT prepare write, setting
this to 0 disables GATT long/reliable writes.
config BLUETOOTH_ATT_TX_MAX
int "Maximum number of queued outgoing ATT PDUs"
default 2
range 1 BLUETOOTH_L2CAP_TX_BUF_COUNT
help
Number of ATT PDUs that can be at a single moment queued for
transmission. If the application tries to send more than this
amount the calls will block until an existing queued PDU gets
sent.
config BLUETOOTH_SMP
bool "Security Manager Protocol support"
select TINYCRYPT

View file

@ -71,6 +71,7 @@ NET_BUF_POOL_DEFINE(prep_pool, CONFIG_BLUETOOTH_ATT_PREPARE_COUNT, BT_ATT_MTU,
enum {
ATT_PENDING_RSP,
ATT_PENDING_CFM,
ATT_DISCONNECTED,
/* Total number of flags - must be at the end of the enum */
ATT_NUM_FLAGS,
@ -84,6 +85,7 @@ struct bt_att {
struct bt_att_req *req;
sys_slist_t reqs;
struct k_delayed_work timeout_work;
struct k_sem tx_sem;
#if CONFIG_BLUETOOTH_ATT_PREPARE_COUNT > 0
struct k_fifo prep_queue;
#endif
@ -104,32 +106,49 @@ static void att_req_destroy(struct bt_att_req *req)
memset(req, 0, sizeof(*req));
}
static void att_clear_flag(struct bt_conn *conn, int flag)
static struct bt_att *att_get(struct bt_conn *conn)
{
#if defined(CONFIG_BLUETOOTH_ATT_ENFORCE_FLOW)
struct bt_l2cap_chan *chan;
struct bt_att *att;
chan = bt_l2cap_le_lookup_tx_cid(conn, BT_L2CAP_CID_ATT);
__ASSERT(chan, "No ATT channel found");
att = CONTAINER_OF(chan, struct bt_att, chan);
atomic_clear_bit(att->flags, flag);
#endif /* CONFIG_BLUETOOTH_ATT_ENFORCE_FLOW */
return CONTAINER_OF(chan, struct bt_att, chan);
}
static void att_cfm_sent(struct bt_conn *conn)
{
BT_DBG("conn %p", conn);
struct bt_att *att = att_get(conn);
att_clear_flag(conn, ATT_PENDING_CFM);
BT_DBG("conn %p att %p", conn, att);
#if defined(CONFIG_BLUETOOTH_ATT_ENFORCE_FLOW)
atomic_clear_bit(att->flags, ATT_PENDING_CFM);
#endif /* CONFIG_BLUETOOTH_ATT_ENFORCE_FLOW */
k_sem_give(&att->tx_sem);
}
static void att_rsp_sent(struct bt_conn *conn)
{
BT_DBG("conn %p", conn);
struct bt_att *att = att_get(conn);
att_clear_flag(conn, ATT_PENDING_RSP);
BT_DBG("conn %p att %p", conn, att);
#if defined(CONFIG_BLUETOOTH_ATT_ENFORCE_FLOW)
atomic_clear_bit(att->flags, ATT_PENDING_RSP);
#endif /* CONFIG_BLUETOOTH_ATT_ENFORCE_FLOW */
k_sem_give(&att->tx_sem);
}
static void att_pdu_sent(struct bt_conn *conn)
{
struct bt_att *att = att_get(conn);
BT_DBG("conn %p att %p", conn, att);
k_sem_give(&att->tx_sem);
}
static bt_conn_tx_cb_t att_cb(struct net_buf *buf)
@ -140,7 +159,7 @@ static bt_conn_tx_cb_t att_cb(struct net_buf *buf)
case ATT_CONFIRMATION:
return att_cfm_sent;
default:
return NULL;
return att_pdu_sent;
}
}
@ -213,12 +232,25 @@ static uint8_t att_mtu_req(struct bt_att *att, struct net_buf *buf)
return 0;
}
static inline bool att_is_connected(struct bt_att *att)
{
return (att->chan.chan.conn->state != BT_CONN_CONNECTED ||
!atomic_test_bit(att->flags, ATT_DISCONNECTED));
}
static int att_send_req(struct bt_att *att, struct bt_att_req *req)
{
BT_DBG("req %p", req);
att->req = req;
k_sem_take(&att->tx_sem, K_FOREVER);
if (!att_is_connected(att)) {
BT_WARN("Disconnected");
k_sem_give(&att->tx_sem);
return -ENOTCONN;
}
/* Save request state so it can be resent */
net_buf_simple_save(&req->buf->b, &req->state);
@ -1851,6 +1883,12 @@ static void bt_att_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
static struct bt_att *att_chan_get(struct bt_conn *conn)
{
struct bt_l2cap_chan *chan;
struct bt_att *att;
if (conn->state != BT_CONN_CONNECTED) {
BT_WARN("Not connected");
return NULL;
}
chan = bt_l2cap_le_lookup_rx_cid(conn, BT_L2CAP_CID_ATT);
if (!chan) {
@ -1858,7 +1896,13 @@ static struct bt_att *att_chan_get(struct bt_conn *conn)
return NULL;
}
return ATT_CHAN(chan);
att = ATT_CHAN(chan);
if (atomic_test_bit(att->flags, ATT_DISCONNECTED)) {
BT_WARN("ATT context flagged as disconnected");
return NULL;
}
return att;
}
struct net_buf *bt_att_create_pdu(struct bt_conn *conn, uint8_t op, size_t len)
@ -1889,6 +1933,7 @@ struct net_buf *bt_att_create_pdu(struct bt_conn *conn, uint8_t op, size_t len)
static void att_reset(struct bt_att *att)
{
struct bt_att_req *req, *tmp;
int i;
#if CONFIG_BLUETOOTH_ATT_PREPARE_COUNT > 0
struct net_buf *buf;
@ -1898,6 +1943,13 @@ static void att_reset(struct bt_att *att)
}
#endif
atomic_set_bit(att->flags, ATT_DISCONNECTED);
/* Ensure that any waiters are woken up */
for (i = 0; i < CONFIG_BLUETOOTH_ATT_TX_MAX; i++) {
k_sem_give(&att->tx_sem);
}
/* Notify pending requests */
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&att->reqs, req, tmp, node) {
if (req->func) {
@ -2001,6 +2053,13 @@ static void bt_att_encrypt_change(struct bt_l2cap_chan *chan,
return;
}
k_sem_take(&att->tx_sem, K_FOREVER);
if (!att_is_connected(att)) {
BT_WARN("Disconnected");
k_sem_give(&att->tx_sem);
return;
}
BT_DBG("Retrying");
/* Resend buffer */
@ -2032,6 +2091,9 @@ static int bt_att_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
}
att->chan.chan.ops = &ops;
atomic_set(att->flags, 0);
k_sem_init(&att->tx_sem, CONFIG_BLUETOOTH_ATT_TX_MAX,
CONFIG_BLUETOOTH_ATT_TX_MAX);
*chan = &att->chan.chan;
@ -2080,6 +2142,13 @@ int bt_att_send(struct bt_conn *conn, struct net_buf *buf)
return -ENOTCONN;
}
k_sem_take(&att->tx_sem, K_FOREVER);
if (!att_is_connected(att)) {
BT_WARN("Disconnected");
k_sem_give(&att->tx_sem);
return -ENOTCONN;
}
hdr = (void *)buf->data;
if (hdr->code == BT_ATT_OP_SIGNED_WRITE_CMD) {
@ -2088,6 +2157,7 @@ int bt_att_send(struct bt_conn *conn, struct net_buf *buf)
err = bt_smp_sign(conn, buf);
if (err) {
BT_ERR("Error signing data");
k_sem_give(&att->tx_sem);
return err;
}
}