Bluetooth: L2CAP_BR: Sending multiple SDU at same time

Improve the retransmission and flow control to support sending
multiple SDU at the same time if the TX windows is not full.

Signed-off-by: Lyle Zhu <lyle.zhu@nxp.com>
This commit is contained in:
Lyle Zhu 2025-02-28 18:27:27 +08:00 committed by Anas Nashif
commit 5fd98e10bd
2 changed files with 189 additions and 156 deletions

View file

@ -374,7 +374,11 @@ struct bt_l2cap_br_window {
/** srej flag */
bool srej;
/* Save PDU state */
struct net_buf_simple_state pdu;
struct net_buf_simple_state sdu_state;
/** @internal Holds the sending buffer. */
struct net_buf *sdu;
/** @internal Total length of TX SDU */
uint16_t sdu_total_len;
};
/** @brief BREDR L2CAP Channel structure. */
@ -403,8 +407,8 @@ struct bt_l2cap_br_chan {
sys_snode_t _pdu_ready;
/** @internal To be used with @ref bt_conn.upper_data_ready */
atomic_t _pdu_ready_lock;
/** @internal Queue of net bufs not yet sent to lower layer */
struct k_fifo _pdu_tx_queue;
/** @internal List of net bufs not yet sent to lower layer */
sys_slist_t _pdu_tx_queue;
#if defined(CONFIG_BT_L2CAP_RET_FC) || defined(__DOXYGEN__)
/** @internal Total length of TX SDU */

View file

@ -149,7 +149,6 @@ enum {
L2CAP_FLAG_FIXED_CONNECTED, /* fixed connected */
/* Retransmition and flow control flags*/
L2CAP_FLAG_SDU_SENDING, /* SDU is sending */
L2CAP_FLAG_RET_TIMER, /* Retransmission timer is working */
L2CAP_FLAG_PDU_RETRANS, /* PDU retransmission */
@ -379,7 +378,7 @@ static uint8_t l2cap_br_get_ident(void)
/* L2CAP channel wants to send a PDU */
static bool chan_has_data(struct bt_l2cap_br_chan *br_chan)
{
return !k_fifo_is_empty(&br_chan->_pdu_tx_queue);
return !sys_slist_is_empty(&br_chan->_pdu_tx_queue);
}
static void raise_data_ready(struct bt_l2cap_br_chan *br_chan)
@ -487,6 +486,15 @@ static struct bt_l2cap_br_window *l2cap_br_find_window(struct bt_l2cap_br_chan *
return win;
}
static void l2cap_br_free_window(struct bt_l2cap_br_chan *br_chan,
struct bt_l2cap_br_window *tx_win)
{
if (tx_win) {
memset(tx_win, 0, sizeof(*tx_win));
k_fifo_put(&br_chan->_free_tx_win, tx_win);
}
}
static uint16_t bt_l2cap_br_update_seq(struct bt_l2cap_br_chan *br_chan, uint16_t seq)
{
if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) ||
@ -553,33 +561,82 @@ static bool bt_l2cap_br_check_req_seq_valid(struct bt_l2cap_br_chan *br_chan, ui
return false;
}
static void l2cap_br_sdu_is_done(struct bt_l2cap_br_chan *br_chan, struct net_buf *sdu, int err)
{
bt_conn_tx_cb_t cb;
__ASSERT(sdu, "Invalid sdu buffer on chan %p", br_chan);
/* The SDU is done */
LOG_DBG("SDU is done, removing %p", sdu);
if (!sys_slist_find_and_remove(&br_chan->_pdu_tx_queue, &sdu->node)) {
LOG_WRN("SDU %p is not found", sdu);
}
cb = closure_cb(sdu->user_data);
if (cb) {
cb(br_chan->chan.conn, closure_data(sdu->user_data), err);
}
/* Remove the pdu */
net_buf_unref(sdu);
LOG_DBG("chan %p done", br_chan);
/* Append channel to list if it still has data */
if (chan_has_data(br_chan)) {
LOG_DBG("chan %p ready", br_chan);
raise_data_ready(br_chan);
}
}
static int bt_l2cap_br_update_req_seq_direct(struct bt_l2cap_br_chan *br_chan, uint16_t req_seq,
bool rej)
{
struct bt_l2cap_br_window *tx_win;
struct net_buf *pdu = k_fifo_peek_head(&br_chan->_pdu_tx_queue);
struct net_buf *sdu;
tx_win = (void *)sys_slist_peek_head(&br_chan->_pdu_outstanding);
while (tx_win && (tx_win->tx_seq != req_seq)) {
sdu = tx_win->sdu;
if ((tx_win->sar == BT_L2CAP_CONTROL_SAR_UNSEG) ||
(tx_win->sar == BT_L2CAP_CONTROL_SAR_END)) {
l2cap_br_sdu_is_done(br_chan, sdu, 0);
}
tx_win = (void *)sys_slist_get(&br_chan->_pdu_outstanding);
memset(tx_win, 0, sizeof(*tx_win));
k_fifo_put(&br_chan->_free_tx_win, tx_win);
l2cap_br_free_window(br_chan, tx_win);
tx_win = (void *)sys_slist_peek_head(&br_chan->_pdu_outstanding);
}
br_chan->expected_ack_seq = req_seq;
if (rej && pdu) {
if (rej) {
tx_win = (void *)sys_slist_peek_head(&br_chan->_pdu_outstanding);
if (tx_win) {
net_buf_simple_restore(&pdu->b, &tx_win->pdu);
sdu = tx_win->sdu;
__ASSERT(sdu, "Invalid sdu buffer on chan %p", br_chan);
net_buf_simple_restore(&sdu->b, &tx_win->sdu_state);
if ((tx_win->sar == BT_L2CAP_CONTROL_SAR_UNSEG) ||
(tx_win->sar == BT_L2CAP_CONTROL_SAR_START)) {
br_chan->_sdu_total_len = 0;
} else {
br_chan->_sdu_total_len = tx_win->sdu_total_len;
}
br_chan->next_tx_seq = tx_win->tx_seq;
}
while (tx_win) {
tx_win = (void *)sys_slist_get(&br_chan->_pdu_outstanding);
memset(tx_win, 0, sizeof(*tx_win));
k_fifo_put(&br_chan->_free_tx_win, tx_win);
l2cap_br_free_window(br_chan, tx_win);
tx_win = (void *)sys_slist_peek_head(&br_chan->_pdu_outstanding);
if (tx_win) {
sdu = tx_win->sdu;
__ASSERT(sdu, "Invalid sdu buffer on chan %p", br_chan);
if ((tx_win->sar == BT_L2CAP_CONTROL_SAR_UNSEG) ||
(tx_win->sar == BT_L2CAP_CONTROL_SAR_START)) {
net_buf_simple_restore(&sdu->b, &tx_win->sdu_state);
}
}
}
}
@ -791,7 +848,7 @@ static bool l2cap_br_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan,
return false;
}
k_fifo_init(&ch->_pdu_tx_queue);
sys_slist_init(&ch->_pdu_tx_queue);
/* All dynamic channels have the destroy handler which makes sure that
* the RTX work structure is properly released with a cancel sync.
@ -848,7 +905,7 @@ int bt_l2cap_br_send_cb(struct bt_conn *conn, uint16_t cid, struct net_buf *buf,
LOG_DBG("push PDU: cb %p userdata %p", cb, user_data);
make_closure(buf->user_data, cb, user_data);
k_fifo_put(&br_chan->_pdu_tx_queue, buf);
sys_slist_append(&br_chan->_pdu_tx_queue, &buf->node);
raise_data_ready(br_chan);
return 0;
@ -1061,6 +1118,27 @@ static void bt_l2cap_br_pack_i_frame_header(struct bt_l2cap_br_chan *br_chan, st
}
}
static struct net_buf *l2cap_br_get_next_sdu(struct bt_l2cap_br_chan *br_chan)
{
struct net_buf *sdu, *next;
if (br_chan->_pdu_buf) {
return br_chan->_pdu_buf;
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&br_chan->_pdu_tx_queue, sdu, next, node) {
if (sdu->len) {
return sdu;
}
if (L2CAP_BR_IS_S_FRAME(closure_data(sdu->user_data))) {
return sdu;
}
}
return NULL;
}
static bool l2cap_br_send_i_frame(struct bt_l2cap_br_chan *br_chan, struct net_buf *sdu)
{
if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_R)) {
@ -1119,132 +1197,84 @@ static struct net_buf *l2cap_br_ret_fc_data_pull(struct bt_conn *conn, size_t am
struct bt_l2cap_br_chan *br_chan =
CONTAINER_OF(pdu_ready, struct bt_l2cap_br_chan, _pdu_ready);
/* Leave the PDU buffer in the queue until we have sent all its
* fragments.
*/
struct net_buf *pdu = k_fifo_peek_head(&br_chan->_pdu_tx_queue);
struct net_buf *send_buf = NULL;
__ASSERT(pdu, "signaled ready but no PDUs in the TX queue");
if (br_chan->_pdu_buf && bt_buf_has_view(br_chan->_pdu_buf)) {
LOG_ERR("already have view on %p of %p", br_chan->_pdu_buf, pdu);
LOG_ERR("already have view on %p", br_chan->_pdu_buf);
return NULL;
}
if (!br_chan->_pdu_buf && !bt_l2cap_br_get_outstanding_count(br_chan)) {
if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_SDU_SENDING) && (!pdu->len)) {
/* There is a sdu has been done */
LOG_DBG("last frag, removing %p", pdu);
__maybe_unused struct net_buf *b;
bt_conn_tx_cb_t cb;
b = k_fifo_get(&br_chan->_pdu_tx_queue, K_NO_WAIT);
__ASSERT_NO_MSG(b == pdu);
atomic_clear_bit(br_chan->flags, L2CAP_FLAG_SDU_SENDING);
br_chan->_pdu_buf = NULL;
br_chan->_pdu_remaining = 0;
cb = closure_cb(pdu->user_data);
if (cb) {
cb(conn, closure_data(pdu->user_data), 0);
}
/* Remove the pdu */
net_buf_unref(pdu);
LOG_DBG("chan %p done", br_chan);
lower_data_ready(br_chan);
/* Append channel to list if it still has data */
if (chan_has_data(br_chan)) {
LOG_DBG("chan %p ready", br_chan);
raise_data_ready(br_chan);
}
return NULL;
}
}
/* Leave the PDU buffer in the queue until we have sent all its
* fragments.
*/
struct net_buf *pdu = l2cap_br_get_next_sdu(br_chan);
struct net_buf *send_buf = NULL;
if (br_chan->_pdu_remaining == 0) {
struct bt_l2cap_br_window *tx_win;
bool first = true;
uint8_t s_bit = BT_L2CAP_CONTROL_S_RR;
bool alloc = false;
send_buf = NULL;
if (pdu && !pdu->len && L2CAP_BR_IS_S_FRAME(closure_data(pdu->user_data))) {
/* It is a S-frame */
s_bit = L2CAP_BR_GET_S_BIT(closure_data(pdu->user_data));
alloc = true;
}
/* There is not any PDU in sending. Check whether needs to send S-frame. */
if (!br_chan->_pdu_buf) {
uint8_t s_bit = BT_L2CAP_CONTROL_S_RR;
bool alloc = false;
if ((atomic_test_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_R) &&
atomic_test_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_R_CHANGED)) ||
(atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ) &&
atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ_CHANGED)) ||
(atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P) &&
atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P_CHANGED)) ||
(atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY) &&
atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY_CHANGED)) ||
atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_S_FRAME)) {
alloc = true;
}
if (!pdu->len && !atomic_test_bit(br_chan->flags, L2CAP_FLAG_SDU_SENDING)) {
if (L2CAP_BR_IS_S_FRAME(closure_data(pdu->user_data))) {
/* It is a S-frame */
s_bit = L2CAP_BR_GET_S_BIT(closure_data(pdu->user_data));
atomic_set_bit(br_chan->flags, L2CAP_FLAG_SDU_SENDING);
alloc = true;
}
if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_P) &&
!l2cap_br_send_i_frame(br_chan, pdu)) {
alloc = true;
}
if (alloc) {
int err;
/* Send S-Frame with R bit set. */
send_buf = bt_l2cap_create_pdu_timeout(&br_tx_pool, 0, K_NO_WAIT);
if (send_buf == NULL) {
/* No buffer can be used for transmission.
* Waiting for buffer available.
*/
LOG_WRN("no buf for sending S-frame on %p", br_chan);
return NULL;
}
if ((atomic_test_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_R) &&
atomic_test_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_R_CHANGED)) ||
(atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ) &&
atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ_CHANGED)) ||
(atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P) &&
atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P_CHANGED)) ||
(atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY) &&
atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY_CHANGED)) ||
atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_S_FRAME)) {
alloc = true;
err = bt_l2cap_br_pack_s_frame_header(br_chan, send_buf, s_bit);
if (err < 0) {
net_buf_unref(send_buf);
LOG_WRN("Cannot sending S-frame on %p", br_chan);
return NULL;
}
if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_P) &&
!l2cap_br_send_i_frame(br_chan, pdu)) {
alloc = true;
atomic_clear_bit(br_chan->flags, L2CAP_FLAG_SEND_S_FRAME);
if (BT_L2CAP_RT_FC_SDU_TAIL_SIZE(br_chan)) {
uint16_t fcs =
crc16_reflect(0xa001, 0, send_buf->data, send_buf->len);
net_buf_add_le16(send_buf, fcs);
}
br_chan->_pdu_remaining = send_buf->len;
br_chan->_pdu_buf = send_buf;
make_closure(send_buf->user_data, NULL, NULL);
if (pdu && !pdu->len && L2CAP_BR_IS_S_FRAME(closure_data(pdu->user_data))) {
l2cap_br_sdu_is_done(br_chan, pdu, 0);
}
if (alloc) {
int err;
/* Send S-Frame with R bit set. */
send_buf = bt_l2cap_create_pdu_timeout(&br_tx_pool, 0, K_NO_WAIT);
if (send_buf == NULL) {
/* No buffer can be used for transmission.
* Waiting for buffer available.
*/
LOG_WRN("no buf for sending S-frame on %p", br_chan);
return NULL;
}
}
if (send_buf != NULL) {
err = bt_l2cap_br_pack_s_frame_header(br_chan, send_buf, s_bit);
if (err < 0) {
net_buf_unref(send_buf);
LOG_WRN("Cannot sending S-frame on %p", br_chan);
return NULL;
}
atomic_clear_bit(br_chan->flags, L2CAP_FLAG_SEND_S_FRAME);
if (BT_L2CAP_RT_FC_SDU_TAIL_SIZE(br_chan)) {
uint16_t fcs = crc16_reflect(0xa001, 0, send_buf->data,
send_buf->len);
net_buf_add_le16(send_buf, fcs);
}
br_chan->_pdu_remaining = send_buf->len;
br_chan->_pdu_buf = send_buf;
make_closure(send_buf->user_data, NULL, NULL);
goto done;
}
goto done;
}
if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_R)) {
@ -1304,6 +1334,15 @@ static struct net_buf *l2cap_br_ret_fc_data_pull(struct bt_conn *conn, size_t am
return NULL;
}
/* Check whether all PDUs have been sent. But not of all are confirmed. */
if (!pdu) {
/* No pending I-frame needs to be sent
*/
LOG_DBG("No pending I-frame needs to be sent on %p", br_chan);
lower_data_ready(br_chan);
return NULL;
}
tx_win = l2cap_br_find_window(br_chan);
if (!tx_win) {
/* No more window for sending I-frame PDU.
@ -1315,26 +1354,6 @@ static struct net_buf *l2cap_br_ret_fc_data_pull(struct bt_conn *conn, size_t am
return NULL;
}
/* Check whether all PDUs have been sent. But not of all are confirmed. */
if (!pdu->len) {
/* All SDU are sent, but not all I-frames have been acked.
* Waiting for all acks.
* Remove the channel from the ready-list, it will be added
* back later when the S-frame PDU has been received.
*/
if (!atomic_test_bit(br_chan->flags, L2CAP_FLAG_SDU_SENDING)) {
/* It is new SDU */
if (L2CAP_BR_IS_S_FRAME(closure_data(pdu->user_data))) {
LOG_DBG("Shouldn't be able to reach here");
__ASSERT(false, "Shouldn't be able to reach here");
}
} else {
LOG_DBG("Waiting for S-frame on %p", br_chan);
lower_data_ready(br_chan);
return NULL;
}
}
send_i_frame:
send_buf = bt_l2cap_create_pdu_timeout(&br_tx_pool, 0, K_NO_WAIT);
if (send_buf == NULL) {
@ -1343,6 +1362,9 @@ send_i_frame:
*/
LOG_WRN("no buf for sending I-frame on %p", br_chan);
/* Maybe we can disconnect channel here if retransmission I-frame. */
/* Free allocated tx_win. */
l2cap_br_free_window(br_chan, tx_win);
return NULL;
}
@ -1357,11 +1379,7 @@ send_i_frame:
bool last_seg;
bool start_seg;
if (atomic_test_and_set_bit(br_chan->flags, L2CAP_FLAG_SDU_SENDING)) {
start_seg = false;
} else {
start_seg = true;
}
start_seg = br_chan->_sdu_total_len ? false : true;
if (start_seg) {
br_chan->_sdu_total_len = pdu->len;
@ -1371,7 +1389,7 @@ send_i_frame:
}
}
net_buf_simple_save(&pdu->b, &tx_win->pdu);
net_buf_simple_save(&pdu->b, &tx_win->sdu_state);
pdu_len = get_pdu_len(br_chan, pdu, start_seg);
@ -1398,6 +1416,7 @@ send_i_frame:
__ASSERT(false, "Tailroom of the buffer cannot be filled %zu / %zu",
net_buf_tailroom(send_buf), actual_pdu_len);
net_buf_unref(send_buf);
l2cap_br_free_window(br_chan, tx_win);
return NULL;
}
@ -1414,6 +1433,8 @@ send_i_frame:
tx_win->srej = false;
tx_win->sar = sar;
tx_win->transmit_counter = 1;
tx_win->sdu_total_len = br_chan->_sdu_total_len;
tx_win->sdu = pdu;
LOG_DBG("Sending I-frame %u: buf %p chan %p len %zu", tx_win->tx_seq, pdu,
br_chan, pdu_len);
@ -1480,8 +1501,7 @@ send_i_frame:
if (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_STREAM) {
sys_slist_append(&br_chan->_pdu_outstanding, &tx_win->node);
} else {
memset(tx_win, 0, sizeof(*tx_win));
k_fifo_put(&br_chan->_free_tx_win, tx_win);
l2cap_br_free_window(br_chan, tx_win);
if (pdu->len == 0) {
l2cap_br_sdu_is_done(br_chan, pdu, 0);
@ -1512,6 +1532,9 @@ done:
} else {
br_chan->_pdu_buf = NULL;
br_chan->_pdu_remaining = 0;
if (pdu && !pdu->len) {
br_chan->_sdu_total_len = 0;
}
LOG_DBG("done sending PDU");
@ -1559,9 +1582,11 @@ struct net_buf *l2cap_br_data_pull(struct bt_conn *conn, size_t amount, size_t *
/* Leave the PDU buffer in the queue until we have sent all its
* fragments.
*/
struct net_buf *pdu = k_fifo_peek_head(&br_chan->_pdu_tx_queue);
const sys_snode_t *tx_pdu = sys_slist_peek_head(&br_chan->_pdu_tx_queue);
__ASSERT(pdu, "signaled ready but no PDUs in the TX queue");
__ASSERT(tx_pdu, "signaled ready but no PDUs in the TX queue");
struct net_buf *pdu = CONTAINER_OF(tx_pdu, struct net_buf, node);
if (bt_buf_has_view(pdu)) {
LOG_ERR("already have view on %p", pdu);
@ -1576,9 +1601,11 @@ struct net_buf *l2cap_br_data_pull(struct bt_conn *conn, size_t amount, size_t *
if (last_frag) {
LOG_DBG("last frag, removing %p", pdu);
__maybe_unused struct net_buf *b = k_fifo_get(&br_chan->_pdu_tx_queue, K_NO_WAIT);
__maybe_unused bool found;
__ASSERT_NO_MSG(b == pdu);
found = sys_slist_find_and_remove(&br_chan->_pdu_tx_queue, &pdu->node);
__ASSERT_NO_MSG(found);
LOG_DBG("chan %p done", br_chan);
lower_data_ready(br_chan);
@ -2461,7 +2488,9 @@ void bt_l2cap_br_chan_del(struct bt_l2cap_chan *chan)
/* Remove buffers on the PDU TX queue. */
while (chan_has_data(br_chan)) {
struct net_buf *buf = k_fifo_get(&br_chan->_pdu_tx_queue, K_NO_WAIT);
const sys_snode_t *tx_buf = sys_slist_get(&br_chan->_pdu_tx_queue);
struct net_buf *buf = CONTAINER_OF(tx_buf, struct net_buf, node);
net_buf_unref(buf);
}