Commit e328140f authored by Mat Martineau's avatar Mat Martineau Committed by Gustavo F. Padovan

Bluetooth: Use event-driven approach for handling ERTM receive buffer

This change moves most L2CAP ERTM receive buffer handling out of the
L2CAP core and in to the socket code.  It's up to the higher layer
(the socket code, in this case) to tell the core when its buffer is
full or has space available.  The recv op should always accept
incoming ERTM data or else the connection will go down.

Within the socket layer, an skb that does not fit in the socket
receive buffer will be temporarily stored.  When the socket is read
from, that skb will be placed in the receive buffer if possible.  Once
adequate buffer space becomes available, the L2CAP core is informed
and the ERTM local busy state is cleared.

Receive buffer management for non-ERTM modes is unchanged.
Signed-off-by: default avatarMat Martineau <mathewm@codeaurora.org>
Signed-off-by: default avatarGustavo F. Padovan <padovan@profusion.mobi>
parent 26f880d2
...@@ -422,6 +422,7 @@ struct l2cap_conn { ...@@ -422,6 +422,7 @@ struct l2cap_conn {
struct l2cap_pinfo { struct l2cap_pinfo {
struct bt_sock bt; struct bt_sock bt;
struct l2cap_chan *chan; struct l2cap_chan *chan;
struct sk_buff *rx_busy_skb;
}; };
enum { enum {
...@@ -498,5 +499,6 @@ void l2cap_chan_close(struct l2cap_chan *chan, int reason); ...@@ -498,5 +499,6 @@ void l2cap_chan_close(struct l2cap_chan *chan, int reason);
void l2cap_chan_destroy(struct l2cap_chan *chan); void l2cap_chan_destroy(struct l2cap_chan *chan);
int l2cap_chan_connect(struct l2cap_chan *chan); int l2cap_chan_connect(struct l2cap_chan *chan);
int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len); int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len);
void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
#endif /* __L2CAP_H */ #endif /* __L2CAP_H */
...@@ -3350,19 +3350,19 @@ static int l2cap_push_rx_skb(struct l2cap_chan *chan, struct sk_buff *skb, u16 c ...@@ -3350,19 +3350,19 @@ static int l2cap_push_rx_skb(struct l2cap_chan *chan, struct sk_buff *skb, u16 c
} }
err = l2cap_ertm_reassembly_sdu(chan, skb, control); err = l2cap_ertm_reassembly_sdu(chan, skb, control);
if (err >= 0) {
chan->buffer_seq = (chan->buffer_seq + 1) % 64; chan->buffer_seq = (chan->buffer_seq + 1) % 64;
return err; return err;
} }
void l2cap_chan_busy(struct l2cap_chan *chan, int busy)
{
if (chan->mode == L2CAP_MODE_ERTM) {
if (busy)
l2cap_ertm_enter_local_busy(chan); l2cap_ertm_enter_local_busy(chan);
else
bt_cb(skb)->sar = control >> L2CAP_CTRL_SAR_SHIFT; l2cap_ertm_exit_local_busy(chan);
__skb_queue_tail(&chan->busy_q, skb); }
queue_work(_busy_wq, &chan->busy_work);
return err;
} }
static int l2cap_streaming_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control) static int l2cap_streaming_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control)
...@@ -3463,13 +3463,22 @@ static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq) ...@@ -3463,13 +3463,22 @@ static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq)
struct sk_buff *skb; struct sk_buff *skb;
u16 control; u16 control;
while ((skb = skb_peek(&chan->srej_q))) { while ((skb = skb_peek(&chan->srej_q)) &&
!test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) {
int err;
if (bt_cb(skb)->tx_seq != tx_seq) if (bt_cb(skb)->tx_seq != tx_seq)
break; break;
skb = skb_dequeue(&chan->srej_q); skb = skb_dequeue(&chan->srej_q);
control = bt_cb(skb)->sar << L2CAP_CTRL_SAR_SHIFT; control = bt_cb(skb)->sar << L2CAP_CTRL_SAR_SHIFT;
l2cap_ertm_reassembly_sdu(chan, skb, control); err = l2cap_ertm_reassembly_sdu(chan, skb, control);
if (err < 0) {
l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
break;
}
chan->buffer_seq_srej = chan->buffer_seq_srej =
(chan->buffer_seq_srej + 1) % 64; (chan->buffer_seq_srej + 1) % 64;
tx_seq = (tx_seq + 1) % 64; tx_seq = (tx_seq + 1) % 64;
...@@ -3625,8 +3634,10 @@ static inline int l2cap_data_channel_iframe(struct l2cap_chan *chan, u16 rx_cont ...@@ -3625,8 +3634,10 @@ static inline int l2cap_data_channel_iframe(struct l2cap_chan *chan, u16 rx_cont
} }
err = l2cap_push_rx_skb(chan, skb, rx_control); err = l2cap_push_rx_skb(chan, skb, rx_control);
if (err < 0) if (err < 0) {
return 0; l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
return err;
}
if (rx_control & L2CAP_CTRL_FINAL) { if (rx_control & L2CAP_CTRL_FINAL) {
if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state)) if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state))
......
...@@ -711,13 +711,15 @@ static int l2cap_sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct ms ...@@ -711,13 +711,15 @@ static int l2cap_sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct ms
static int l2cap_sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len, int flags) static int l2cap_sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len, int flags)
{ {
struct sock *sk = sock->sk; struct sock *sk = sock->sk;
struct l2cap_pinfo *pi = l2cap_pi(sk);
int err;
lock_sock(sk); lock_sock(sk);
if (sk->sk_state == BT_CONNECT2 && bt_sk(sk)->defer_setup) { if (sk->sk_state == BT_CONNECT2 && bt_sk(sk)->defer_setup) {
sk->sk_state = BT_CONFIG; sk->sk_state = BT_CONFIG;
__l2cap_connect_rsp_defer(l2cap_pi(sk)->chan); __l2cap_connect_rsp_defer(pi->chan);
release_sock(sk); release_sock(sk);
return 0; return 0;
} }
...@@ -725,9 +727,37 @@ static int l2cap_sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct ms ...@@ -725,9 +727,37 @@ static int l2cap_sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct ms
release_sock(sk); release_sock(sk);
if (sock->type == SOCK_STREAM) if (sock->type == SOCK_STREAM)
return bt_sock_stream_recvmsg(iocb, sock, msg, len, flags); err = bt_sock_stream_recvmsg(iocb, sock, msg, len, flags);
else
err = bt_sock_recvmsg(iocb, sock, msg, len, flags);
if (pi->chan->mode != L2CAP_MODE_ERTM)
return err;
/* Attempt to put pending rx data in the socket buffer */
lock_sock(sk);
if (!test_bit(CONN_LOCAL_BUSY, &pi->chan->conn_state))
goto done;
if (pi->rx_busy_skb) {
if (!sock_queue_rcv_skb(sk, pi->rx_busy_skb))
pi->rx_busy_skb = NULL;
else
goto done;
}
/* Restore data flow when half of the receive buffer is
* available. This avoids resending large numbers of
* frames.
*/
if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf >> 1)
l2cap_chan_busy(pi->chan, 0);
return bt_sock_recvmsg(iocb, sock, msg, len, flags); done:
release_sock(sk);
return err;
} }
/* Kill socket (only if zapped and orphan) /* Kill socket (only if zapped and orphan)
...@@ -811,9 +841,31 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(void *data) ...@@ -811,9 +841,31 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(void *data)
static int l2cap_sock_recv_cb(void *data, struct sk_buff *skb) static int l2cap_sock_recv_cb(void *data, struct sk_buff *skb)
{ {
int err;
struct sock *sk = data; struct sock *sk = data;
struct l2cap_pinfo *pi = l2cap_pi(sk);
if (pi->rx_busy_skb)
return -ENOMEM;
err = sock_queue_rcv_skb(sk, skb);
return sock_queue_rcv_skb(sk, skb); /* For ERTM, handle one skb that doesn't fit into the recv
* buffer. This is important to do because the data frames
* have already been acked, so the skb cannot be discarded.
*
* Notify the l2cap core that the buffer is full, so the
* LOCAL_BUSY state is entered and no more frames are
* acked and reassembled until there is buffer space
* available.
*/
if (err < 0 && pi->chan->mode == L2CAP_MODE_ERTM) {
pi->rx_busy_skb = skb;
l2cap_chan_busy(pi->chan, 1);
err = 0;
}
return err;
} }
static void l2cap_sock_close_cb(void *data) static void l2cap_sock_close_cb(void *data)
...@@ -842,6 +894,11 @@ static void l2cap_sock_destruct(struct sock *sk) ...@@ -842,6 +894,11 @@ static void l2cap_sock_destruct(struct sock *sk)
{ {
BT_DBG("sk %p", sk); BT_DBG("sk %p", sk);
if (l2cap_pi(sk)->rx_busy_skb) {
kfree_skb(l2cap_pi(sk)->rx_busy_skb);
l2cap_pi(sk)->rx_busy_skb = NULL;
}
skb_queue_purge(&sk->sk_receive_queue); skb_queue_purge(&sk->sk_receive_queue);
skb_queue_purge(&sk->sk_write_queue); skb_queue_purge(&sk->sk_write_queue);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment