Bluetooth: Add support for deferring RFCOMM connection setup
[pandora-kernel.git] / net / bluetooth / rfcomm / sock.c
index ad00cbf..d37a829 100644 (file)
@@ -262,8 +262,10 @@ static void rfcomm_sock_init(struct sock *sk, struct sock *parent)
        if (parent) {
                sk->sk_type = parent->sk_type;
                pi->link_mode = rfcomm_pi(parent)->link_mode;
+               pi->dlc->defer_setup = bt_sk(parent)->defer_setup;
        } else {
                pi->link_mode = 0;
+               pi->dlc->defer_setup = 0;
        }
 
        pi->dlc->link_mode = pi->link_mode;
@@ -554,6 +556,9 @@ static int rfcomm_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
        struct sk_buff *skb;
        int sent = 0;
 
+       if (test_bit(RFCOMM_DEFER_SETUP, &d->flags))
+               return -ENOTCONN;
+
        if (msg->msg_flags & MSG_OOB)
                return -EOPNOTSUPP;
 
@@ -570,8 +575,11 @@ static int rfcomm_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
 
                skb = sock_alloc_send_skb(sk, size + RFCOMM_SKB_RESERVE,
                                msg->msg_flags & MSG_DONTWAIT, &err);
-               if (!skb)
+               if (!skb) {
+                       if (sent == 0)
+                               sent = err;
                        break;
+               }
                skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE);
 
                err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size);
@@ -630,10 +638,16 @@ static int rfcomm_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
                               struct msghdr *msg, size_t size, int flags)
 {
        struct sock *sk = sock->sk;
+       struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc;
        int err = 0;
        size_t target, copied = 0;
        long timeo;
 
+       if (test_and_clear_bit(RFCOMM_DEFER_SETUP, &d->flags)) {
+               rfcomm_dlc_accept(d);
+               return 0;
+       }
+
        if (flags & MSG_OOB)
                return -EOPNOTSUPP;
 
@@ -710,7 +724,7 @@ out:
        return copied ? : err;
 }
 
-static int rfcomm_sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, int optlen)
+static int rfcomm_sock_setsockopt_old(struct socket *sock, int optname, char __user *optval, int optlen)
 {
        struct sock *sk = sock->sk;
        int err = 0;
@@ -739,7 +753,44 @@ static int rfcomm_sock_setsockopt(struct socket *sock, int level, int optname, c
        return err;
 }
 
-static int rfcomm_sock_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
+static int rfcomm_sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, int optlen)
+{
+       struct sock *sk = sock->sk;
+       int err = 0;
+       u32 opt;
+
+       BT_DBG("sk %p", sk);
+
+       if (level == SOL_RFCOMM)
+               return rfcomm_sock_setsockopt_old(sock, optname, optval, optlen);
+
+       lock_sock(sk);
+
+       switch (optname) {
+       case BT_DEFER_SETUP:
+               if (sk->sk_state != BT_BOUND && sk->sk_state != BT_LISTEN) {
+                       err = -EINVAL;
+                       break;
+               }
+
+               if (get_user(opt, (u32 __user *) optval)) {
+                       err = -EFAULT;
+                       break;
+               }
+
+               bt_sk(sk)->defer_setup = opt;
+               break;
+
+       default:
+               err = -ENOPROTOOPT;
+               break;
+       }
+
+       release_sock(sk);
+       return err;
+}
+
+static int rfcomm_sock_getsockopt_old(struct socket *sock, int optname, char __user *optval, int __user *optlen)
 {
        struct sock *sk = sock->sk;
        struct sock *l2cap_sk;
@@ -760,7 +811,8 @@ static int rfcomm_sock_getsockopt(struct socket *sock, int level, int optname, c
                break;
 
        case RFCOMM_CONNINFO:
-               if (sk->sk_state != BT_CONNECTED) {
+               if (sk->sk_state != BT_CONNECTED &&
+                                       !rfcomm_pi(sk)->dlc->defer_setup) {
                        err = -ENOTCONN;
                        break;
                }
@@ -785,11 +837,45 @@ static int rfcomm_sock_getsockopt(struct socket *sock, int level, int optname, c
        return err;
 }
 
-static int rfcomm_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+static int rfcomm_sock_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
 {
-#if defined(CONFIG_BT_RFCOMM_TTY) || defined(CONFIG_BT_RFCOMM_DEBUG)
        struct sock *sk = sock->sk;
-#endif
+       int len, err = 0;
+
+       BT_DBG("sk %p", sk);
+
+       if (level == SOL_RFCOMM)
+               return rfcomm_sock_getsockopt_old(sock, optname, optval, optlen);
+
+       if (get_user(len, optlen))
+               return -EFAULT;
+
+       lock_sock(sk);
+
+       switch (optname) {
+       case BT_DEFER_SETUP:
+               if (sk->sk_state != BT_BOUND && sk->sk_state != BT_LISTEN) {
+                       err = -EINVAL;
+                       break;
+               }
+
+               if (put_user(bt_sk(sk)->defer_setup, (u32 __user *) optval))
+                       err = -EFAULT;
+
+               break;
+
+       default:
+               err = -ENOPROTOOPT;
+               break;
+       }
+
+       release_sock(sk);
+       return err;
+}
+
+static int rfcomm_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+       struct sock *sk __maybe_unused = sock->sk;
        int err;
 
        BT_DBG("sk %p cmd %x arg %lx", sk, cmd, arg);
@@ -890,6 +976,10 @@ int rfcomm_connect_ind(struct rfcomm_session *s, u8 channel, struct rfcomm_dlc *
 
 done:
        bh_unlock_sock(parent);
+
+       if (bt_sk(parent)->defer_setup)
+               parent->sk_state_change(parent);
+
        return result;
 }