Bluetooth: host: copy fragment data at the last minute

Only copy the data from the 'parent' buf into the segment buf if we
successfully acquired the HCI semaphore (ie there are available controller
buffers).

This way we avoid pulling and pushing back the data in the case where there
aren't any controller buffers available.

Signed-off-by: Jonathan Rico <jonathan.rico@nordicsemi.no>
This commit is contained in:
Jonathan Rico 2022-11-21 13:06:07 +01:00 committed by Fabio Baltieri
commit ca51439cd1

View file

@ -504,33 +504,32 @@ static int send_iso(struct bt_conn *conn, struct net_buf *buf, uint8_t flags)
return bt_send(buf);
}
static int send_frag(struct bt_conn *conn, struct net_buf *buf, uint8_t flags,
bool always_consume)
static inline uint16_t conn_mtu(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.mtu;
}
#endif /* CONFIG_BT_BREDR */
#if defined(CONFIG_BT_ISO)
if (conn->type == BT_CONN_TYPE_ISO && bt_dev.le.iso_mtu) {
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;
unsigned int key;
int err = 0;
/* Check if the controller can accept ACL packets */
if (k_sem_take(bt_conn_get_pkts(conn), K_NO_WAIT)) {
/* not `goto fail`, we don't want to free the tx context: in the
* case where it is the original buffer, it will contain the
* callback ptr.
*/
LOG_DBG("no CTLR bufs");
return -ENOBUFS;
}
if (flags == FRAG_SINGLE || flags == FRAG_END) {
/* 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);
}
/* Check for disconnection while waiting for pkts_sem */
if (conn->state != BT_CONN_CONNECTED) {
err = -ENOTCONN;
@ -601,35 +600,41 @@ fail:
conn_tx_destroy(conn, tx);
}
if (always_consume) {
net_buf_unref(buf);
}
return err;
}
static inline uint16_t conn_mtu(struct bt_conn *conn)
static int send_frag(struct bt_conn *conn,
struct net_buf *buf, struct net_buf *frag,
uint8_t flags)
{
#if defined(CONFIG_BT_BREDR)
if (conn->type == BT_CONN_TYPE_BR || !bt_dev.le.acl_mtu) {
return bt_dev.br.mtu;
/* 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;
}
#endif /* CONFIG_BT_BREDR */
#if defined(CONFIG_BT_ISO)
if (conn->type == BT_CONN_TYPE_ISO && bt_dev.le.iso_mtu) {
return bt_dev.le.iso_mtu;
/* 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;
}
#endif /* CONFIG_BT_ISO */
#if defined(CONFIG_BT_CONN)
return bt_dev.le.acl_mtu;
#else
return 0;
#endif /* CONFIG_BT_CONN */
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;
uint16_t frag_len;
switch (conn->type) {
#if defined(CONFIG_BT_ISO)
@ -655,11 +660,6 @@ static struct net_buf *create_frag(struct bt_conn *conn, struct net_buf *buf)
tx_data(frag)->tx = NULL;
tx_data(frag)->is_cont = false;
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);
return frag;
}
@ -674,13 +674,13 @@ static int send_buf(struct bt_conn *conn, struct net_buf *buf)
/* 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, FRAG_SINGLE, false);
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.
* buffer (which works since we've used net_buf_pull on it).
*/
flags = FRAG_START;
if (tx_data(buf)->is_cont) {
@ -693,12 +693,10 @@ static int send_buf(struct bt_conn *conn, struct net_buf *buf)
return -ENOMEM;
}
err = send_frag(conn, frag, flags, false);
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;
/* Put the frag back into the original buffer */
net_buf_push_mem(buf, frag->data, frag->len);
net_buf_unref(frag);
return err;
}
@ -708,7 +706,7 @@ static int send_buf(struct bt_conn *conn, struct net_buf *buf)
LOG_DBG("last frag");
tx_data(buf)->is_cont = true;
return send_frag(conn, buf, FRAG_END, false);
return send_frag(conn, buf, NULL, FRAG_END);
}
static struct k_poll_signal conn_change =