Bluetooth: L2CAP: Offload processing of tx_queue to a work
This offloads the processing of tx_queue to a work so the callbacks calling resume don't start sending packets directly which can cause stack overflow. Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
parent
99066db21d
commit
cf84216b1a
2 changed files with 83 additions and 61 deletions
|
@ -126,6 +126,8 @@ struct bt_l2cap_le_chan {
|
||||||
struct k_fifo tx_queue;
|
struct k_fifo tx_queue;
|
||||||
/** Channel Pending Transmission buffer */
|
/** Channel Pending Transmission buffer */
|
||||||
struct net_buf *tx_buf;
|
struct net_buf *tx_buf;
|
||||||
|
/** Channel Transmission work */
|
||||||
|
struct k_work tx_work;
|
||||||
/** Segment SDU packet from upper layer */
|
/** Segment SDU packet from upper layer */
|
||||||
struct net_buf *_sdu;
|
struct net_buf *_sdu;
|
||||||
u16_t _sdu_len;
|
u16_t _sdu_len;
|
||||||
|
|
|
@ -724,6 +724,46 @@ static void l2cap_chan_rx_init(struct bt_l2cap_le_chan *chan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct net_buf *l2cap_chan_le_get_tx_buf(struct bt_l2cap_le_chan *ch)
|
||||||
|
{
|
||||||
|
struct net_buf *buf;
|
||||||
|
|
||||||
|
/* Return current buffer */
|
||||||
|
if (ch->tx_buf) {
|
||||||
|
buf = ch->tx_buf;
|
||||||
|
ch->tx_buf = NULL;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
return net_buf_get(&ch->tx_queue, K_NO_WAIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l2cap_chan_le_send_sdu(struct bt_l2cap_le_chan *ch,
|
||||||
|
struct net_buf **buf, u16_t sent);
|
||||||
|
|
||||||
|
static void l2cap_chan_tx_process(struct k_work *work)
|
||||||
|
{
|
||||||
|
struct bt_l2cap_le_chan *ch;
|
||||||
|
struct net_buf *buf;
|
||||||
|
|
||||||
|
ch = CONTAINER_OF(work, struct bt_l2cap_le_chan, tx_work);
|
||||||
|
|
||||||
|
/* Resume tx in case there are buffers in the queue */
|
||||||
|
while ((buf = l2cap_chan_le_get_tx_buf(ch))) {
|
||||||
|
int sent = data_sent(buf)->len;
|
||||||
|
|
||||||
|
BT_DBG("buf %p sent %u", buf, sent);
|
||||||
|
|
||||||
|
sent = l2cap_chan_le_send_sdu(ch, &buf, sent);
|
||||||
|
if (sent < 0) {
|
||||||
|
if (sent == -EAGAIN) {
|
||||||
|
ch->tx_buf = buf;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void l2cap_chan_tx_init(struct bt_l2cap_le_chan *chan)
|
static void l2cap_chan_tx_init(struct bt_l2cap_le_chan *chan)
|
||||||
{
|
{
|
||||||
BT_DBG("chan %p", chan);
|
BT_DBG("chan %p", chan);
|
||||||
|
@ -731,6 +771,7 @@ static void l2cap_chan_tx_init(struct bt_l2cap_le_chan *chan)
|
||||||
(void)memset(&chan->tx, 0, sizeof(chan->tx));
|
(void)memset(&chan->tx, 0, sizeof(chan->tx));
|
||||||
k_sem_init(&chan->tx.credits, 0, UINT_MAX);
|
k_sem_init(&chan->tx.credits, 0, UINT_MAX);
|
||||||
k_fifo_init(&chan->tx_queue);
|
k_fifo_init(&chan->tx_queue);
|
||||||
|
k_work_init(&chan->tx_work, l2cap_chan_tx_process);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void l2cap_chan_tx_give_credits(struct bt_l2cap_le_chan *chan,
|
static void l2cap_chan_tx_give_credits(struct bt_l2cap_le_chan *chan,
|
||||||
|
@ -1180,7 +1221,15 @@ segment:
|
||||||
return seg;
|
return seg;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void l2cap_chan_le_send_resume(struct bt_l2cap_le_chan *ch);
|
static void l2cap_chan_tx_resume(struct bt_l2cap_le_chan *ch)
|
||||||
|
{
|
||||||
|
if (!k_sem_count_get(&ch->tx.credits) ||
|
||||||
|
(k_fifo_is_empty(&ch->tx_queue) && !ch->tx_buf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_work_submit(&ch->tx_work);
|
||||||
|
}
|
||||||
|
|
||||||
static void l2cap_chan_sdu_sent(struct bt_conn *conn, void *user_data)
|
static void l2cap_chan_sdu_sent(struct bt_conn *conn, void *user_data)
|
||||||
{
|
{
|
||||||
|
@ -1192,7 +1241,7 @@ static void l2cap_chan_sdu_sent(struct bt_conn *conn, void *user_data)
|
||||||
chan->ops->sent(chan);
|
chan->ops->sent(chan);
|
||||||
}
|
}
|
||||||
|
|
||||||
l2cap_chan_le_send_resume(BT_L2CAP_LE_CHAN(chan));
|
l2cap_chan_tx_resume(BT_L2CAP_LE_CHAN(chan));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void l2cap_chan_seg_sent(struct bt_conn *conn, void *user_data)
|
static void l2cap_chan_seg_sent(struct bt_conn *conn, void *user_data)
|
||||||
|
@ -1201,14 +1250,14 @@ static void l2cap_chan_seg_sent(struct bt_conn *conn, void *user_data)
|
||||||
|
|
||||||
BT_DBG("conn %p chan %p", conn, chan);
|
BT_DBG("conn %p chan %p", conn, chan);
|
||||||
|
|
||||||
l2cap_chan_le_send_resume(BT_L2CAP_LE_CHAN(chan));
|
l2cap_chan_tx_resume(BT_L2CAP_LE_CHAN(chan));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int l2cap_chan_le_send(struct bt_l2cap_le_chan *ch, struct net_buf *buf,
|
static int l2cap_chan_le_send(struct bt_l2cap_le_chan *ch, struct net_buf *buf,
|
||||||
u16_t sdu_hdr_len)
|
u16_t sdu_hdr_len)
|
||||||
{
|
{
|
||||||
struct net_buf *seg;
|
struct net_buf *seg;
|
||||||
int len;
|
int len, err;
|
||||||
|
|
||||||
/* Wait for credits */
|
/* Wait for credits */
|
||||||
if (k_sem_take(&ch->tx.credits, K_NO_WAIT)) {
|
if (k_sem_take(&ch->tx.credits, K_NO_WAIT)) {
|
||||||
|
@ -1224,7 +1273,7 @@ static int l2cap_chan_le_send(struct bt_l2cap_le_chan *ch, struct net_buf *buf,
|
||||||
|
|
||||||
/* Channel may have been disconnected while waiting for a buffer */
|
/* Channel may have been disconnected while waiting for a buffer */
|
||||||
if (!ch->chan.conn) {
|
if (!ch->chan.conn) {
|
||||||
net_buf_unref(buf);
|
net_buf_unref(seg);
|
||||||
return -ECONNRESET;
|
return -ECONNRESET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1237,11 +1286,22 @@ static int l2cap_chan_le_send(struct bt_l2cap_le_chan *ch, struct net_buf *buf,
|
||||||
* callback has been set.
|
* callback has been set.
|
||||||
*/
|
*/
|
||||||
if ((buf == seg || !buf->len) && ch->chan.ops->sent) {
|
if ((buf == seg || !buf->len) && ch->chan.ops->sent) {
|
||||||
bt_l2cap_send_cb(ch->chan.conn, ch->tx.cid, seg,
|
err = bt_l2cap_send_cb(ch->chan.conn, ch->tx.cid, seg,
|
||||||
l2cap_chan_sdu_sent, &ch->chan);
|
l2cap_chan_sdu_sent, &ch->chan);
|
||||||
} else {
|
} else {
|
||||||
bt_l2cap_send_cb(ch->chan.conn, ch->tx.cid, seg,
|
err = bt_l2cap_send_cb(ch->chan.conn, ch->tx.cid, seg,
|
||||||
l2cap_chan_seg_sent, &ch->chan);
|
l2cap_chan_seg_sent, &ch->chan);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
BT_WARN("Unable to send seg %d", err);
|
||||||
|
k_sem_give(&ch->tx.credits);
|
||||||
|
|
||||||
|
if (err == -ENOBUFS) {
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if there is no credits left clear output status and notify its
|
/* Check if there is no credits left clear output status and notify its
|
||||||
|
@ -1314,44 +1374,6 @@ static int l2cap_chan_le_send_sdu(struct bt_l2cap_le_chan *ch,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct net_buf *l2cap_chan_le_get_tx_buf(struct bt_l2cap_le_chan *ch)
|
|
||||||
{
|
|
||||||
struct net_buf *buf;
|
|
||||||
|
|
||||||
/* Return current buffer */
|
|
||||||
if (ch->tx_buf) {
|
|
||||||
buf = ch->tx_buf;
|
|
||||||
ch->tx_buf = NULL;
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
return net_buf_get(&ch->tx_queue, K_NO_WAIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void l2cap_chan_le_send_resume(struct bt_l2cap_le_chan *ch)
|
|
||||||
{
|
|
||||||
struct net_buf *buf;
|
|
||||||
|
|
||||||
if (!k_sem_count_get(&ch->tx.credits)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Resume tx in case there are buffers in the queue */
|
|
||||||
while ((buf = l2cap_chan_le_get_tx_buf(ch))) {
|
|
||||||
u16_t sent = data_sent(buf)->len;
|
|
||||||
|
|
||||||
BT_DBG("buf %p sent %u", buf, sent);
|
|
||||||
|
|
||||||
sent = l2cap_chan_le_send_sdu(ch, &buf, sent);
|
|
||||||
if (sent < 0) {
|
|
||||||
if (sent == -EAGAIN) {
|
|
||||||
ch->tx_buf = buf;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void le_credits(struct bt_l2cap *l2cap, u8_t ident,
|
static void le_credits(struct bt_l2cap *l2cap, u8_t ident,
|
||||||
struct net_buf *buf)
|
struct net_buf *buf)
|
||||||
{
|
{
|
||||||
|
@ -1390,7 +1412,7 @@ static void le_credits(struct bt_l2cap *l2cap, u8_t ident,
|
||||||
BT_DBG("chan %p total credits %u", ch,
|
BT_DBG("chan %p total credits %u", ch,
|
||||||
k_sem_count_get(&ch->tx.credits));
|
k_sem_count_get(&ch->tx.credits));
|
||||||
|
|
||||||
l2cap_chan_le_send_resume(ch);
|
l2cap_chan_tx_resume(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reject_cmd(struct bt_l2cap *l2cap, u8_t ident,
|
static void reject_cmd(struct bt_l2cap *l2cap, u8_t ident,
|
||||||
|
@ -1491,14 +1513,16 @@ static void l2cap_chan_send_credits(struct bt_l2cap_le_chan *chan,
|
||||||
credits = chan->rx.init_credits;
|
credits = chan->rx.init_credits;
|
||||||
}
|
}
|
||||||
|
|
||||||
l2cap_chan_rx_give_credits(chan, credits);
|
|
||||||
|
|
||||||
buf = l2cap_create_le_sig_pdu(buf, BT_L2CAP_LE_CREDITS, get_ident(),
|
buf = l2cap_create_le_sig_pdu(buf, BT_L2CAP_LE_CREDITS, get_ident(),
|
||||||
sizeof(*ev));
|
sizeof(*ev));
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
|
BT_ERR("Unable to send credits update");
|
||||||
|
bt_l2cap_chan_disconnect(&chan->chan);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l2cap_chan_rx_give_credits(chan, credits);
|
||||||
|
|
||||||
ev = net_buf_add(buf, sizeof(*ev));
|
ev = net_buf_add(buf, sizeof(*ev));
|
||||||
ev->cid = sys_cpu_to_le16(chan->rx.cid);
|
ev->cid = sys_cpu_to_le16(chan->rx.cid);
|
||||||
ev->credits = sys_cpu_to_le16(credits);
|
ev->credits = sys_cpu_to_le16(credits);
|
||||||
|
@ -1919,6 +1943,7 @@ int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
|
||||||
|
|
||||||
int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
||||||
{
|
{
|
||||||
|
struct bt_l2cap_le_chan *ch = BT_L2CAP_LE_CHAN(chan);
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
|
@ -1936,24 +1961,19 @@ int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
||||||
return bt_l2cap_br_chan_send(chan, buf);
|
return bt_l2cap_br_chan_send(chan, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Attempt to resume first since there could be data from previous
|
/* Queue if there pending segments left from previous packets */
|
||||||
* packets pending.
|
if (ch->tx_buf || !k_fifo_is_empty(&ch->tx_queue)) {
|
||||||
*/
|
|
||||||
l2cap_chan_le_send_resume(BT_L2CAP_LE_CHAN(chan));
|
|
||||||
|
|
||||||
/* Check if there are still pending segments */
|
|
||||||
if (BT_L2CAP_LE_CHAN(chan)->tx_buf) {
|
|
||||||
/* Queue buffer to be sent later */
|
|
||||||
data_sent(buf)->len = 0;
|
data_sent(buf)->len = 0;
|
||||||
net_buf_put(&(BT_L2CAP_LE_CHAN(chan))->tx_queue, buf);
|
net_buf_put(&ch->tx_queue, buf);
|
||||||
|
k_work_submit(&ch->tx_work);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l2cap_chan_le_send_sdu(BT_L2CAP_LE_CHAN(chan), &buf, 0);
|
err = l2cap_chan_le_send_sdu(ch, &buf, 0);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
if (err == -EAGAIN && data_sent(buf)->len) {
|
if (err == -EAGAIN && data_sent(buf)->len) {
|
||||||
/* Queue buffer if at least one segment could be sent */
|
/* Queue buffer if at least one segment could be sent */
|
||||||
net_buf_put(&(BT_L2CAP_LE_CHAN(chan))->tx_queue, buf);
|
net_buf_put(&ch->tx_queue, buf);
|
||||||
return data_sent(buf)->len;
|
return data_sent(buf)->len;
|
||||||
}
|
}
|
||||||
BT_ERR("failed to send message %d", err);
|
BT_ERR("failed to send message %d", err);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue