[Bluetooth] Support create connection cancel command
authorMarcel Holtmann <marcel@holtmann.org>
Tue, 26 Sep 2006 07:43:48 +0000 (09:43 +0200)
committerDavid S. Miller <davem@sunset.davemloft.net>
Fri, 29 Sep 2006 01:01:33 +0000 (18:01 -0700)
In case of non-blocking connects it is possible that the last user
of an ACL link quits before the connection has been fully established.
This will lead to a race condition where the internal state of a
connection is closed, but the actual link has been established and is
active. In case of Bluetooth 1.2 and later devices it is possible to
call create connection cancel to abort the connect. For older devices
the disconnect timer will be used to trigger the needed disconnect.

Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
include/net/bluetooth/hci.h
include/net/bluetooth/hci_core.h
net/bluetooth/hci_conn.c
net/bluetooth/hci_event.c

index 5f04181b8109a6d6722c727c1cf03c3a775be900..10a3eec191fd2bf6110fbd0b4dadaa6fbdf2af8a 100644 (file)
@@ -297,6 +297,7 @@ struct hci_cp_host_buffer_size {
 
 /* Link Control */
 #define OGF_LINK_CTL   0x01 
+
 #define OCF_CREATE_CONN                0x0005
 struct hci_cp_create_conn {
        bdaddr_t bdaddr;
@@ -307,6 +308,11 @@ struct hci_cp_create_conn {
        __u8     role_switch;
 } __attribute__ ((packed));
 
+#define OCF_CREATE_CONN_CANCEL 0x0008
+struct hci_cp_create_conn_cancel {
+       bdaddr_t bdaddr;
+} __attribute__ ((packed));
+
 #define OCF_ACCEPT_CONN_REQ    0x0009
 struct hci_cp_accept_conn_req {
        bdaddr_t bdaddr;
index 7451a9c92d9d5d06733498a6baaf43dd26ddc186..df22efcfcc0b7deceb77ee1094a05d24f00c22d2 100644 (file)
@@ -316,10 +316,13 @@ static inline void hci_conn_put(struct hci_conn *conn)
        if (atomic_dec_and_test(&conn->refcnt)) {
                unsigned long timeo;
                if (conn->type == ACL_LINK) {
-                       timeo = msecs_to_jiffies(HCI_DISCONN_TIMEOUT);
-                       if (!conn->out)
-                               timeo *= 2;
                        del_timer(&conn->idle_timer);
+                       if (conn->state == BT_CONNECTED) {
+                               timeo = msecs_to_jiffies(HCI_DISCONN_TIMEOUT);
+                               if (!conn->out)
+                                       timeo *= 2;
+                       } else
+                               timeo = msecs_to_jiffies(10);
                } else
                        timeo = msecs_to_jiffies(10);
                mod_timer(&conn->disc_timer, jiffies + timeo);
index 7e9515b41cc0f9e2e64e27991000688848d6905c..90e3a285a17eaf9a4bde198879748d8a4da680a7 100644 (file)
@@ -84,6 +84,20 @@ static void hci_acl_connect(struct hci_conn *conn)
        hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CREATE_CONN, sizeof(cp), &cp);
 }
 
+static void hci_acl_connect_cancel(struct hci_conn *conn)
+{
+       struct hci_cp_create_conn_cancel cp;
+
+       BT_DBG("%p", conn);
+
+       if (conn->hdev->hci_ver < 2)
+               return;
+
+       bacpy(&cp.bdaddr, &conn->dst);
+       hci_send_cmd(conn->hdev, OGF_LINK_CTL,
+                               OCF_CREATE_CONN_CANCEL, sizeof(cp), &cp);
+}
+
 void hci_acl_disconn(struct hci_conn *conn, __u8 reason)
 {
        struct hci_cp_disconnect cp;
@@ -94,7 +108,8 @@ void hci_acl_disconn(struct hci_conn *conn, __u8 reason)
 
        cp.handle = __cpu_to_le16(conn->handle);
        cp.reason = reason;
-       hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_DISCONNECT, sizeof(cp), &cp);
+       hci_send_cmd(conn->hdev, OGF_LINK_CTL,
+                               OCF_DISCONNECT, sizeof(cp), &cp);
 }
 
 void hci_add_sco(struct hci_conn *conn, __u16 handle)
@@ -124,12 +139,20 @@ static void hci_conn_timeout(unsigned long arg)
                return;
 
        hci_dev_lock(hdev);
-       if (conn->state == BT_CONNECTED)
+
+       switch (conn->state) {
+       case BT_CONNECT:
+               hci_acl_connect_cancel(conn);
+               break;
+       case BT_CONNECTED:
                hci_acl_disconn(conn, 0x13);
-       else
+               break;
+       default:
                conn->state = BT_CLOSED;
+               break;
+       }
+
        hci_dev_unlock(hdev);
-       return;
 }
 
 static void hci_conn_idle(unsigned long arg)
index 7518bdbf34cd18df8fc28637a162ee57ab934545..bb25484b87475ba66d80eeb51bd1df2ae9ba7b8e 100644 (file)
@@ -750,6 +750,8 @@ static inline void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *s
                if (test_bit(HCI_ENCRYPT, &hdev->flags))
                        conn->link_mode |= HCI_LM_ENCRYPT;
 
+               hci_conn_hold(conn);
+
                /* Get remote features */
                if (conn->type == ACL_LINK) {
                        struct hci_cp_read_remote_features cp;
@@ -778,6 +780,8 @@ static inline void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *s
                        hci_send_cmd(hdev, OGF_LINK_CTL,
                                OCF_CHANGE_CONN_PTYPE, sizeof(cp), &cp);
                }
+
+               hci_conn_put(conn);
        } else
                conn->state = BT_CLOSED;