diff options
Diffstat (limited to 'net/bluetooth/l2cap_sock.c')
| -rw-r--r-- | net/bluetooth/l2cap_sock.c | 95 | 
1 files changed, 75 insertions, 20 deletions
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 5cc83f906c12..6db60946c627 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -327,7 +327,7 @@ done:  }  static int l2cap_sock_accept(struct socket *sock, struct socket *newsock, -			     int flags, bool kern) +			     struct proto_accept_arg *arg)  {  	DEFINE_WAIT_FUNC(wait, woken_wake_function);  	struct sock *sk = sock->sk, *nsk; @@ -336,7 +336,7 @@ static int l2cap_sock_accept(struct socket *sock, struct socket *newsock,  	lock_sock_nested(sk, L2CAP_NESTING_PARENT); -	timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); +	timeo = sock_rcvtimeo(sk, arg->flags & O_NONBLOCK);  	BT_DBG("sk %p timeo %ld", sk, timeo); @@ -1131,6 +1131,34 @@ static int l2cap_sock_sendmsg(struct socket *sock, struct msghdr *msg,  	return err;  } +static void l2cap_publish_rx_avail(struct l2cap_chan *chan) +{ +	struct sock *sk = chan->data; +	ssize_t avail = sk->sk_rcvbuf - atomic_read(&sk->sk_rmem_alloc); +	int expected_skbs, skb_overhead; + +	if (avail <= 0) { +		l2cap_chan_rx_avail(chan, 0); +		return; +	} + +	if (!chan->mps) { +		l2cap_chan_rx_avail(chan, -1); +		return; +	} + +	/* Correct available memory by estimated sk_buff overhead. +	 * This is significant due to small transfer sizes. However, accept +	 * at least one full packet if receive space is non-zero. +	 */ +	expected_skbs = DIV_ROUND_UP(avail, chan->mps); +	skb_overhead = expected_skbs * sizeof(struct sk_buff); +	if (skb_overhead < avail) +		l2cap_chan_rx_avail(chan, avail - skb_overhead); +	else +		l2cap_chan_rx_avail(chan, -1); +} +  static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg,  			      size_t len, int flags)  { @@ -1167,28 +1195,33 @@ static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg,  	else  		err = bt_sock_recvmsg(sock, msg, len, flags); -	if (pi->chan->mode != L2CAP_MODE_ERTM) +	if (pi->chan->mode != L2CAP_MODE_ERTM && +	    pi->chan->mode != L2CAP_MODE_LE_FLOWCTL && +	    pi->chan->mode != L2CAP_MODE_EXT_FLOWCTL)  		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; +	l2cap_publish_rx_avail(pi->chan); -	if (pi->rx_busy_skb) { -		if (!__sock_queue_rcv_skb(sk, pi->rx_busy_skb)) -			pi->rx_busy_skb = NULL; -		else +	/* Attempt to put pending rx data in the socket buffer */ +	while (!list_empty(&pi->rx_busy)) { +		struct l2cap_rx_busy *rx_busy = +			list_first_entry(&pi->rx_busy, +					 struct l2cap_rx_busy, +					 list); +		if (__sock_queue_rcv_skb(sk, rx_busy->skb) < 0)  			goto done; +		list_del(&rx_busy->list); +		kfree(rx_busy);  	}  	/* 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) +	if (test_bit(CONN_LOCAL_BUSY, &pi->chan->conn_state) && +	    atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf >> 1)  		l2cap_chan_busy(pi->chan, 0);  done: @@ -1449,17 +1482,20 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)  static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)  {  	struct sock *sk = chan->data; +	struct l2cap_pinfo *pi = l2cap_pi(sk);  	int err;  	lock_sock(sk); -	if (l2cap_pi(sk)->rx_busy_skb) { +	if (chan->mode == L2CAP_MODE_ERTM && !list_empty(&pi->rx_busy)) {  		err = -ENOMEM;  		goto done;  	}  	if (chan->mode != L2CAP_MODE_ERTM && -	    chan->mode != L2CAP_MODE_STREAMING) { +	    chan->mode != L2CAP_MODE_STREAMING && +	    chan->mode != L2CAP_MODE_LE_FLOWCTL && +	    chan->mode != L2CAP_MODE_EXT_FLOWCTL) {  		/* Even if no filter is attached, we could potentially  		 * get errors from security modules, etc.  		 */ @@ -1470,7 +1506,9 @@ static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)  	err = __sock_queue_rcv_skb(sk, skb); -	/* For ERTM, handle one skb that doesn't fit into the recv +	l2cap_publish_rx_avail(chan); + +	/* For ERTM and LE, handle a 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.  	 * @@ -1479,8 +1517,18 @@ static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)  	 * acked and reassembled until there is buffer space  	 * available.  	 */ -	if (err < 0 && chan->mode == L2CAP_MODE_ERTM) { -		l2cap_pi(sk)->rx_busy_skb = skb; +	if (err < 0 && +	    (chan->mode == L2CAP_MODE_ERTM || +	     chan->mode == L2CAP_MODE_LE_FLOWCTL || +	     chan->mode == L2CAP_MODE_EXT_FLOWCTL)) { +		struct l2cap_rx_busy *rx_busy = +			kmalloc(sizeof(*rx_busy), GFP_KERNEL); +		if (!rx_busy) { +			err = -ENOMEM; +			goto done; +		} +		rx_busy->skb = skb; +		list_add_tail(&rx_busy->list, &pi->rx_busy);  		l2cap_chan_busy(chan, 1);  		err = 0;  	} @@ -1706,6 +1754,8 @@ static const struct l2cap_ops l2cap_chan_ops = {  static void l2cap_sock_destruct(struct sock *sk)  { +	struct l2cap_rx_busy *rx_busy, *next; +  	BT_DBG("sk %p", sk);  	if (l2cap_pi(sk)->chan) { @@ -1713,9 +1763,10 @@ static void l2cap_sock_destruct(struct sock *sk)  		l2cap_chan_put(l2cap_pi(sk)->chan);  	} -	if (l2cap_pi(sk)->rx_busy_skb) { -		kfree_skb(l2cap_pi(sk)->rx_busy_skb); -		l2cap_pi(sk)->rx_busy_skb = NULL; +	list_for_each_entry_safe(rx_busy, next, &l2cap_pi(sk)->rx_busy, list) { +		kfree_skb(rx_busy->skb); +		list_del(&rx_busy->list); +		kfree(rx_busy);  	}  	skb_queue_purge(&sk->sk_receive_queue); @@ -1799,6 +1850,8 @@ static void l2cap_sock_init(struct sock *sk, struct sock *parent)  	chan->data = sk;  	chan->ops = &l2cap_chan_ops; + +	l2cap_publish_rx_avail(chan);  }  static struct proto l2cap_proto = { @@ -1820,6 +1873,8 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,  	sk->sk_destruct = l2cap_sock_destruct;  	sk->sk_sndtimeo = L2CAP_CONN_TIMEOUT; +	INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy); +  	chan = l2cap_chan_create();  	if (!chan) {  		sk_free(sk);  |