gss_krb5: add support for triple-des encryption
[safe/jmp/linux-2.6] / net / bluetooth / hci_conn.c
index 5c0c2b1..b10e3cd 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
    BlueZ - Bluetooth protocol stack for Linux
    Copyright (C) 2000-2001 Qualcomm Incorporated
 
    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
    IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
-   CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES 
-   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
-   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
+   CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-   ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, 
-   COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS 
+   ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+   COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
    SOFTWARE IS DISCLAIMED.
 */
 
@@ -29,7 +29,6 @@
 #include <linux/types.h>
 #include <linux/errno.h>
 #include <linux/kernel.h>
-#include <linux/sched.h>
 #include <linux/slab.h>
 #include <linux/poll.h>
 #include <linux/fcntl.h>
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
 
-#ifndef CONFIG_BT_HCI_CORE_DEBUG
-#undef  BT_DBG
-#define BT_DBG(D...)
-#endif
-
-static void hci_acl_connect(struct hci_conn *conn)
+void hci_acl_connect(struct hci_conn *conn)
 {
        struct hci_dev *hdev = conn->hdev;
        struct inquiry_entry *ie;
@@ -60,28 +54,50 @@ static void hci_acl_connect(struct hci_conn *conn)
        BT_DBG("%p", conn);
 
        conn->state = BT_CONNECT;
-       conn->out   = 1;
+       conn->out = 1;
+
        conn->link_mode = HCI_LM_MASTER;
 
+       conn->attempt++;
+
+       conn->link_policy = hdev->link_policy;
+
        memset(&cp, 0, sizeof(cp));
        bacpy(&cp.bdaddr, &conn->dst);
        cp.pscan_rep_mode = 0x02;
 
-       if ((ie = hci_inquiry_cache_lookup(hdev, &conn->dst)) &&
-                       inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) {
-               cp.pscan_rep_mode = ie->data.pscan_rep_mode;
-               cp.pscan_mode     = ie->data.pscan_mode;
-               cp.clock_offset   = ie->data.clock_offset | __cpu_to_le16(0x8000);
+       if ((ie = hci_inquiry_cache_lookup(hdev, &conn->dst))) {
+               if (inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) {
+                       cp.pscan_rep_mode = ie->data.pscan_rep_mode;
+                       cp.pscan_mode     = ie->data.pscan_mode;
+                       cp.clock_offset   = ie->data.clock_offset |
+                                                       cpu_to_le16(0x8000);
+               }
+
                memcpy(conn->dev_class, ie->data.dev_class, 3);
+               conn->ssp_mode = ie->data.ssp_mode;
        }
 
-       cp.pkt_type = __cpu_to_le16(hdev->pkt_type & ACL_PTYPE_MASK);
+       cp.pkt_type = cpu_to_le16(conn->pkt_type);
        if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER))
-               cp.role_switch  = 0x01;
+               cp.role_switch = 0x01;
        else
-               cp.role_switch  = 0x00;
-               
-       hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CREATE_CONN, sizeof(cp), &cp);
+               cp.role_switch = 0x00;
+
+       hci_send_cmd(hdev, HCI_OP_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, HCI_OP_CREATE_CONN_CANCEL, sizeof(cp), &cp);
 }
 
 void hci_acl_disconn(struct hci_conn *conn, __u8 reason)
@@ -92,9 +108,9 @@ void hci_acl_disconn(struct hci_conn *conn, __u8 reason)
 
        conn->state = BT_DISCONN;
 
-       cp.handle = __cpu_to_le16(conn->handle);
+       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, HCI_OP_DISCONNECT, sizeof(cp), &cp);
 }
 
 void hci_add_sco(struct hci_conn *conn, __u16 handle)
@@ -107,16 +123,43 @@ void hci_add_sco(struct hci_conn *conn, __u16 handle)
        conn->state = BT_CONNECT;
        conn->out = 1;
 
-       cp.pkt_type = __cpu_to_le16(hdev->pkt_type & SCO_PTYPE_MASK);
-       cp.handle   = __cpu_to_le16(handle);
+       conn->attempt++;
+
+       cp.handle   = cpu_to_le16(handle);
+       cp.pkt_type = cpu_to_le16(conn->pkt_type);
+
+       hci_send_cmd(hdev, HCI_OP_ADD_SCO, sizeof(cp), &cp);
+}
+
+void hci_setup_sync(struct hci_conn *conn, __u16 handle)
+{
+       struct hci_dev *hdev = conn->hdev;
+       struct hci_cp_setup_sync_conn cp;
+
+       BT_DBG("%p", conn);
+
+       conn->state = BT_CONNECT;
+       conn->out = 1;
+
+       conn->attempt++;
+
+       cp.handle   = cpu_to_le16(handle);
+       cp.pkt_type = cpu_to_le16(conn->pkt_type);
 
-       hci_send_cmd(hdev, OGF_LINK_CTL, OCF_ADD_SCO, sizeof(cp), &cp);
+       cp.tx_bandwidth   = cpu_to_le32(0x00001f40);
+       cp.rx_bandwidth   = cpu_to_le32(0x00001f40);
+       cp.max_latency    = cpu_to_le16(0xffff);
+       cp.voice_setting  = cpu_to_le16(hdev->voice_setting);
+       cp.retrans_effort = 0xff;
+
+       hci_send_cmd(hdev, HCI_OP_SETUP_SYNC_CONN, sizeof(cp), &cp);
 }
 
 static void hci_conn_timeout(unsigned long arg)
 {
-       struct hci_conn *conn = (void *)arg;
-       struct hci_dev  *hdev = conn->hdev;
+       struct hci_conn *conn = (void *) arg;
+       struct hci_dev *hdev = conn->hdev;
+       __u8 reason;
 
        BT_DBG("conn %p state %d", conn, conn->state);
 
@@ -124,19 +167,33 @@ static void hci_conn_timeout(unsigned long arg)
                return;
 
        hci_dev_lock(hdev);
-       if (conn->state == BT_CONNECTED)
-               hci_acl_disconn(conn, 0x13);
-       else
+
+       switch (conn->state) {
+       case BT_CONNECT:
+       case BT_CONNECT2:
+               if (conn->type == ACL_LINK && conn->out)
+                       hci_acl_connect_cancel(conn);
+               break;
+       case BT_CONFIG:
+       case BT_CONNECTED:
+               reason = hci_proto_disconn_ind(conn);
+               hci_acl_disconn(conn, reason);
+               break;
+       default:
                conn->state = BT_CLOSED;
+               break;
+       }
+
        hci_dev_unlock(hdev);
-       return;
 }
 
-static void hci_conn_init_timer(struct hci_conn *conn)
+static void hci_conn_idle(unsigned long arg)
 {
-       init_timer(&conn->timer);
-       conn->timer.function = hci_conn_timeout;
-       conn->timer.data = (unsigned long)conn;
+       struct hci_conn *conn = (void *) arg;
+
+       BT_DBG("conn %p mode %d", conn, conn->mode);
+
+       hci_conn_enter_sniff_mode(conn);
 }
 
 struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst)
@@ -145,17 +202,40 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst)
 
        BT_DBG("%s dst %s", hdev->name, batostr(dst));
 
-       if (!(conn = kmalloc(sizeof(struct hci_conn), GFP_ATOMIC)))
+       conn = kzalloc(sizeof(struct hci_conn), GFP_ATOMIC);
+       if (!conn)
                return NULL;
-       memset(conn, 0, sizeof(struct hci_conn));
 
        bacpy(&conn->dst, dst);
-       conn->type   = type;
-       conn->hdev   = hdev;
-       conn->state  = BT_OPEN;
+       conn->hdev  = hdev;
+       conn->type  = type;
+       conn->mode  = HCI_CM_ACTIVE;
+       conn->state = BT_OPEN;
+       conn->auth_type = HCI_AT_GENERAL_BONDING;
+
+       conn->power_save = 1;
+       conn->disc_timeout = HCI_DISCONN_TIMEOUT;
+
+       switch (type) {
+       case ACL_LINK:
+               conn->pkt_type = hdev->pkt_type & ACL_PTYPE_MASK;
+               break;
+       case SCO_LINK:
+               if (lmp_esco_capable(hdev))
+                       conn->pkt_type = (hdev->esco_type & SCO_ESCO_MASK) |
+                                       (hdev->esco_type & EDR_ESCO_MASK);
+               else
+                       conn->pkt_type = hdev->pkt_type & SCO_PTYPE_MASK;
+               break;
+       case ESCO_LINK:
+               conn->pkt_type = hdev->esco_type & ~EDR_ESCO_MASK;
+               break;
+       }
 
        skb_queue_head_init(&conn->data_q);
-       hci_conn_init_timer(conn);
+
+       setup_timer(&conn->disc_timer, hci_conn_timeout, (unsigned long)conn);
+       setup_timer(&conn->idle_timer, hci_conn_idle, (unsigned long)conn);
 
        atomic_set(&conn->refcnt, 0);
 
@@ -167,6 +247,10 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst)
        if (hdev->notify)
                hdev->notify(hdev, HCI_NOTIFY_CONN_ADD);
 
+       atomic_set(&conn->devref, 0);
+
+       hci_conn_init_sysfs(conn);
+
        tasklet_enable(&hdev->tx_task);
 
        return conn;
@@ -178,21 +262,23 @@ int hci_conn_del(struct hci_conn *conn)
 
        BT_DBG("%s conn %p handle %d", hdev->name, conn, conn->handle);
 
-       hci_conn_del_timer(conn);
+       del_timer(&conn->idle_timer);
 
-       if (conn->type == SCO_LINK) {
-               struct hci_conn *acl = conn->link;
-               if (acl) {
-                       acl->link = NULL;
-                       hci_conn_put(acl);
-               }
-       } else {
+       del_timer(&conn->disc_timer);
+
+       if (conn->type == ACL_LINK) {
                struct hci_conn *sco = conn->link;
                if (sco)
                        sco->link = NULL;
 
                /* Unacked frames */
                hdev->acl_cnt += conn->sent;
+       } else {
+               struct hci_conn *acl = conn->link;
+               if (acl) {
+                       acl->link = NULL;
+                       hci_conn_put(acl);
+               }
        }
 
        tasklet_disable(&hdev->tx_task);
@@ -205,9 +291,10 @@ int hci_conn_del(struct hci_conn *conn)
 
        skb_queue_purge(&conn->data_q);
 
+       hci_conn_put_device(conn);
+
        hci_dev_put(hdev);
 
-       kfree(conn);
        return 0;
 }
 
@@ -227,7 +314,7 @@ struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src)
                if (!test_bit(HCI_UP, &d->flags) || test_bit(HCI_RAW, &d->flags))
                        continue;
 
-               /* Simple routing: 
+               /* Simple routing:
                 *   No source address - find interface with bdaddr != dst
                 *   Source address    - find interface with bdaddr == src
                 */
@@ -253,9 +340,10 @@ EXPORT_SYMBOL(hci_get_route);
 
 /* Create SCO or ACL connection.
  * Device _must_ be locked */
-struct hci_conn * hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst)
+struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8 sec_level, __u8 auth_type)
 {
        struct hci_conn *acl;
+       struct hci_conn *sco;
 
        BT_DBG("%s dst %s", hdev->name, batostr(dst));
 
@@ -266,71 +354,106 @@ struct hci_conn * hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst)
 
        hci_conn_hold(acl);
 
-       if (acl->state == BT_OPEN || acl->state == BT_CLOSED)
+       if (acl->state == BT_OPEN || acl->state == BT_CLOSED) {
+               acl->sec_level = sec_level;
+               acl->auth_type = auth_type;
                hci_acl_connect(acl);
+       }
 
-       if (type == SCO_LINK) {
-               struct hci_conn *sco;
+       if (type == ACL_LINK)
+               return acl;
 
-               if (!(sco = hci_conn_hash_lookup_ba(hdev, SCO_LINK, dst))) {
-                       if (!(sco = hci_conn_add(hdev, SCO_LINK, dst))) {
-                               hci_conn_put(acl);
-                               return NULL;
-                       }
+       if (!(sco = hci_conn_hash_lookup_ba(hdev, type, dst))) {
+               if (!(sco = hci_conn_add(hdev, type, dst))) {
+                       hci_conn_put(acl);
+                       return NULL;
                }
-               acl->link = sco;
-               sco->link = acl;
+       }
 
-               hci_conn_hold(sco);
+       acl->link = sco;
+       sco->link = acl;
 
-               if (acl->state == BT_CONNECTED && 
-                               (sco->state == BT_OPEN || sco->state == BT_CLOSED))
-                       hci_add_sco(sco, acl->handle);
+       hci_conn_hold(sco);
 
-               return sco;
-       } else {
-               return acl;
+       if (acl->state == BT_CONNECTED &&
+                       (sco->state == BT_OPEN || sco->state == BT_CLOSED)) {
+               acl->power_save = 1;
+               hci_conn_enter_active_mode(acl);
+
+               if (lmp_esco_capable(hdev))
+                       hci_setup_sync(sco, acl->handle);
+               else
+                       hci_add_sco(sco, acl->handle);
        }
+
+       return sco;
 }
 EXPORT_SYMBOL(hci_connect);
 
+/* Check link security requirement */
+int hci_conn_check_link_mode(struct hci_conn *conn)
+{
+       BT_DBG("conn %p", conn);
+
+       if (conn->ssp_mode > 0 && conn->hdev->ssp_mode > 0 &&
+                                       !(conn->link_mode & HCI_LM_ENCRYPT))
+               return 0;
+
+       return 1;
+}
+EXPORT_SYMBOL(hci_conn_check_link_mode);
+
 /* Authenticate remote device */
-int hci_conn_auth(struct hci_conn *conn)
+static int hci_conn_auth(struct hci_conn *conn, __u8 sec_level, __u8 auth_type)
 {
        BT_DBG("conn %p", conn);
 
-       if (conn->link_mode & HCI_LM_AUTH)
+       if (sec_level > conn->sec_level)
+               conn->sec_level = sec_level;
+       else if (conn->link_mode & HCI_LM_AUTH)
                return 1;
 
+       conn->auth_type = auth_type;
+
        if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) {
                struct hci_cp_auth_requested cp;
-               cp.handle = __cpu_to_le16(conn->handle);
-               hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_AUTH_REQUESTED, sizeof(cp), &cp);
+               cp.handle = cpu_to_le16(conn->handle);
+               hci_send_cmd(conn->hdev, HCI_OP_AUTH_REQUESTED,
+                                                       sizeof(cp), &cp);
        }
+
        return 0;
 }
-EXPORT_SYMBOL(hci_conn_auth);
 
-/* Enable encryption */
-int hci_conn_encrypt(struct hci_conn *conn)
+/* Enable security */
+int hci_conn_security(struct hci_conn *conn, __u8 sec_level, __u8 auth_type)
 {
        BT_DBG("conn %p", conn);
 
-       if (conn->link_mode & HCI_LM_ENCRYPT)
+       if (sec_level == BT_SECURITY_SDP)
+               return 1;
+
+       if (sec_level == BT_SECURITY_LOW &&
+                               (!conn->ssp_mode || !conn->hdev->ssp_mode))
                return 1;
 
+       if (conn->link_mode & HCI_LM_ENCRYPT)
+               return hci_conn_auth(conn, sec_level, auth_type);
+
        if (test_and_set_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend))
                return 0;
 
-       if (hci_conn_auth(conn)) {
+       if (hci_conn_auth(conn, sec_level, auth_type)) {
                struct hci_cp_set_conn_encrypt cp;
-               cp.handle  = __cpu_to_le16(conn->handle);
-               cp.encrypt = 1; 
-               hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT, sizeof(cp), &cp);
+               cp.handle  = cpu_to_le16(conn->handle);
+               cp.encrypt = 1;
+               hci_send_cmd(conn->hdev, HCI_OP_SET_CONN_ENCRYPT,
+                                                       sizeof(cp), &cp);
        }
+
        return 0;
 }
-EXPORT_SYMBOL(hci_conn_encrypt);
+EXPORT_SYMBOL(hci_conn_security);
 
 /* Change link key */
 int hci_conn_change_link_key(struct hci_conn *conn)
@@ -339,15 +462,17 @@ int hci_conn_change_link_key(struct hci_conn *conn)
 
        if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) {
                struct hci_cp_change_conn_link_key cp;
-               cp.handle = __cpu_to_le16(conn->handle);
-               hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_CHANGE_CONN_LINK_KEY, sizeof(cp), &cp);
+               cp.handle = cpu_to_le16(conn->handle);
+               hci_send_cmd(conn->hdev, HCI_OP_CHANGE_CONN_LINK_KEY,
+                                                       sizeof(cp), &cp);
        }
+
        return 0;
 }
 EXPORT_SYMBOL(hci_conn_change_link_key);
 
 /* Switch role */
-int hci_conn_switch_role(struct hci_conn *conn, uint8_t role)
+int hci_conn_switch_role(struct hci_conn *conn, __u8 role)
 {
        BT_DBG("conn %p", conn);
 
@@ -358,12 +483,74 @@ int hci_conn_switch_role(struct hci_conn *conn, uint8_t role)
                struct hci_cp_switch_role cp;
                bacpy(&cp.bdaddr, &conn->dst);
                cp.role = role;
-               hci_send_cmd(conn->hdev, OGF_LINK_POLICY, OCF_SWITCH_ROLE, sizeof(cp), &cp);
+               hci_send_cmd(conn->hdev, HCI_OP_SWITCH_ROLE, sizeof(cp), &cp);
        }
+
        return 0;
 }
 EXPORT_SYMBOL(hci_conn_switch_role);
 
+/* Enter active mode */
+void hci_conn_enter_active_mode(struct hci_conn *conn)
+{
+       struct hci_dev *hdev = conn->hdev;
+
+       BT_DBG("conn %p mode %d", conn, conn->mode);
+
+       if (test_bit(HCI_RAW, &hdev->flags))
+               return;
+
+       if (conn->mode != HCI_CM_SNIFF || !conn->power_save)
+               goto timer;
+
+       if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) {
+               struct hci_cp_exit_sniff_mode cp;
+               cp.handle = cpu_to_le16(conn->handle);
+               hci_send_cmd(hdev, HCI_OP_EXIT_SNIFF_MODE, sizeof(cp), &cp);
+       }
+
+timer:
+       if (hdev->idle_timeout > 0)
+               mod_timer(&conn->idle_timer,
+                       jiffies + msecs_to_jiffies(hdev->idle_timeout));
+}
+
+/* Enter sniff mode */
+void hci_conn_enter_sniff_mode(struct hci_conn *conn)
+{
+       struct hci_dev *hdev = conn->hdev;
+
+       BT_DBG("conn %p mode %d", conn, conn->mode);
+
+       if (test_bit(HCI_RAW, &hdev->flags))
+               return;
+
+       if (!lmp_sniff_capable(hdev) || !lmp_sniff_capable(conn))
+               return;
+
+       if (conn->mode != HCI_CM_ACTIVE || !(conn->link_policy & HCI_LP_SNIFF))
+               return;
+
+       if (lmp_sniffsubr_capable(hdev) && lmp_sniffsubr_capable(conn)) {
+               struct hci_cp_sniff_subrate cp;
+               cp.handle             = cpu_to_le16(conn->handle);
+               cp.max_latency        = cpu_to_le16(0);
+               cp.min_remote_timeout = cpu_to_le16(0);
+               cp.min_local_timeout  = cpu_to_le16(0);
+               hci_send_cmd(hdev, HCI_OP_SNIFF_SUBRATE, sizeof(cp), &cp);
+       }
+
+       if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) {
+               struct hci_cp_sniff_mode cp;
+               cp.handle       = cpu_to_le16(conn->handle);
+               cp.max_interval = cpu_to_le16(hdev->sniff_max_interval);
+               cp.min_interval = cpu_to_le16(hdev->sniff_min_interval);
+               cp.attempt      = cpu_to_le16(4);
+               cp.timeout      = cpu_to_le16(1);
+               hci_send_cmd(hdev, HCI_OP_SNIFF_MODE, sizeof(cp), &cp);
+       }
+}
+
 /* Drop all connection on the device */
 void hci_conn_hash_flush(struct hci_dev *hdev)
 {
@@ -381,11 +568,40 @@ void hci_conn_hash_flush(struct hci_dev *hdev)
 
                c->state = BT_CLOSED;
 
-               hci_proto_disconn_ind(c, 0x16);
+               hci_proto_disconn_cfm(c, 0x16);
                hci_conn_del(c);
        }
 }
 
+/* Check pending connect attempts */
+void hci_conn_check_pending(struct hci_dev *hdev)
+{
+       struct hci_conn *conn;
+
+       BT_DBG("hdev %s", hdev->name);
+
+       hci_dev_lock(hdev);
+
+       conn = hci_conn_hash_lookup_state(hdev, ACL_LINK, BT_CONNECT2);
+       if (conn)
+               hci_acl_connect(conn);
+
+       hci_dev_unlock(hdev);
+}
+
+void hci_conn_hold_device(struct hci_conn *conn)
+{
+       atomic_inc(&conn->devref);
+}
+EXPORT_SYMBOL(hci_conn_hold_device);
+
+void hci_conn_put_device(struct hci_conn *conn)
+{
+       if (atomic_dec_and_test(&conn->devref))
+               hci_conn_del_sysfs(conn);
+}
+EXPORT_SYMBOL(hci_conn_put_device);
+
 int hci_get_conn_list(void __user *arg)
 {
        struct hci_conn_list_req req, *cl;
@@ -467,3 +683,23 @@ int hci_get_conn_info(struct hci_dev *hdev, void __user *arg)
 
        return copy_to_user(ptr, &ci, sizeof(ci)) ? -EFAULT : 0;
 }
+
+int hci_get_auth_info(struct hci_dev *hdev, void __user *arg)
+{
+       struct hci_auth_info_req req;
+       struct hci_conn *conn;
+
+       if (copy_from_user(&req, arg, sizeof(req)))
+               return -EFAULT;
+
+       hci_dev_lock_bh(hdev);
+       conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &req.bdaddr);
+       if (conn)
+               req.type = conn->auth_type;
+       hci_dev_unlock_bh(hdev);
+
+       if (!conn)
+               return -ENOENT;
+
+       return copy_to_user(arg, &req, sizeof(req)) ? -EFAULT : 0;
+}