diff options
Diffstat (limited to 'kernel/net/bluetooth/l2cap_sock.c')
-rw-r--r-- | kernel/net/bluetooth/l2cap_sock.c | 104 |
1 files changed, 76 insertions, 28 deletions
diff --git a/kernel/net/bluetooth/l2cap_sock.c b/kernel/net/bluetooth/l2cap_sock.c index a7278f05e..1bb551527 100644 --- a/kernel/net/bluetooth/l2cap_sock.c +++ b/kernel/net/bluetooth/l2cap_sock.c @@ -43,7 +43,7 @@ static struct bt_sock_list l2cap_sk_list = { static const struct proto_ops l2cap_sock_ops; static void l2cap_sock_init(struct sock *sk, struct sock *parent); static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, - int proto, gfp_t prio); + int proto, gfp_t prio, int kern); bool l2cap_is_socket(struct socket *sock) { @@ -1054,18 +1054,23 @@ static void l2cap_sock_kill(struct sock *sk) sock_put(sk); } -static int __l2cap_wait_ack(struct sock *sk) +static int __l2cap_wait_ack(struct sock *sk, struct l2cap_chan *chan) { - struct l2cap_chan *chan = l2cap_pi(sk)->chan; DECLARE_WAITQUEUE(wait, current); int err = 0; - int timeo = HZ/5; + int timeo = L2CAP_WAIT_ACK_POLL_PERIOD; + /* Timeout to prevent infinite loop */ + unsigned long timeout = jiffies + L2CAP_WAIT_ACK_TIMEOUT; add_wait_queue(sk_sleep(sk), &wait); set_current_state(TASK_INTERRUPTIBLE); - while (chan->unacked_frames > 0 && chan->conn) { + do { + BT_DBG("Waiting for %d ACKs, timeout %04d ms", + chan->unacked_frames, time_after(jiffies, timeout) ? 0 : + jiffies_to_msecs(timeout - jiffies)); + if (!timeo) - timeo = HZ/5; + timeo = L2CAP_WAIT_ACK_POLL_PERIOD; if (signal_pending(current)) { err = sock_intr_errno(timeo); @@ -1080,7 +1085,15 @@ static int __l2cap_wait_ack(struct sock *sk) err = sock_error(sk); if (err) break; - } + + if (time_after(jiffies, timeout)) { + err = -ENOLINK; + break; + } + + } while (chan->unacked_frames > 0 && + chan->state == BT_CONNECTED); + set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); return err; @@ -1098,41 +1111,76 @@ static int l2cap_sock_shutdown(struct socket *sock, int how) if (!sk) return 0; + lock_sock(sk); + + if (sk->sk_shutdown) + goto shutdown_already; + + BT_DBG("Handling sock shutdown"); + + /* prevent sk structure from being freed whilst unlocked */ + sock_hold(sk); + chan = l2cap_pi(sk)->chan; - conn = chan->conn; + /* prevent chan structure from being freed whilst unlocked */ + l2cap_chan_hold(chan); BT_DBG("chan %p state %s", chan, state_to_string(chan->state)); + if (chan->mode == L2CAP_MODE_ERTM && + chan->unacked_frames > 0 && + chan->state == BT_CONNECTED) { + err = __l2cap_wait_ack(sk, chan); + + /* After waiting for ACKs, check whether shutdown + * has already been actioned to close the L2CAP + * link such as by l2cap_disconnection_req(). + */ + if (sk->sk_shutdown) + goto has_shutdown; + } + + sk->sk_shutdown = SHUTDOWN_MASK; + release_sock(sk); + + l2cap_chan_lock(chan); + conn = chan->conn; + if (conn) + /* prevent conn structure from being freed */ + l2cap_conn_get(conn); + l2cap_chan_unlock(chan); + if (conn) + /* mutex lock must be taken before l2cap_chan_lock() */ mutex_lock(&conn->chan_lock); l2cap_chan_lock(chan); - lock_sock(sk); + l2cap_chan_close(chan, 0); + l2cap_chan_unlock(chan); - if (!sk->sk_shutdown) { - if (chan->mode == L2CAP_MODE_ERTM) - err = __l2cap_wait_ack(sk); + if (conn) { + mutex_unlock(&conn->chan_lock); + l2cap_conn_put(conn); + } - sk->sk_shutdown = SHUTDOWN_MASK; + lock_sock(sk); - release_sock(sk); - l2cap_chan_close(chan, 0); - lock_sock(sk); + if (sock_flag(sk, SOCK_LINGER) && sk->sk_lingertime && + !(current->flags & PF_EXITING)) + err = bt_sock_wait_state(sk, BT_CLOSED, + sk->sk_lingertime); - if (sock_flag(sk, SOCK_LINGER) && sk->sk_lingertime && - !(current->flags & PF_EXITING)) - err = bt_sock_wait_state(sk, BT_CLOSED, - sk->sk_lingertime); - } +has_shutdown: + l2cap_chan_put(chan); + sock_put(sk); +shutdown_already: if (!err && sk->sk_err) err = -sk->sk_err; release_sock(sk); - l2cap_chan_unlock(chan); - if (conn) - mutex_unlock(&conn->chan_lock); + BT_DBG("Sock shutdown complete err: %d", err); return err; } @@ -1193,7 +1241,7 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan) } sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP, - GFP_ATOMIC); + GFP_ATOMIC, 0); if (!sk) { release_sock(parent); return NULL; @@ -1523,12 +1571,12 @@ static struct proto l2cap_proto = { }; static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, - int proto, gfp_t prio) + int proto, gfp_t prio, int kern) { struct sock *sk; struct l2cap_chan *chan; - sk = sk_alloc(net, PF_BLUETOOTH, prio, &l2cap_proto); + sk = sk_alloc(net, PF_BLUETOOTH, prio, &l2cap_proto, kern); if (!sk) return NULL; @@ -1574,7 +1622,7 @@ static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol, sock->ops = &l2cap_sock_ops; - sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC); + sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern); if (!sk) return -ENOMEM; |