Bluetooth: Fix limited discoverable mode for Zeevo modules
[pandora-kernel.git] / net / bluetooth / mgmt.c
index fb7fc9f..22cf547 100644 (file)
@@ -536,6 +536,18 @@ static u8 *create_uuid128_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
        return ptr;
 }
 
+static struct pending_cmd *mgmt_pending_find(u16 opcode, struct hci_dev *hdev)
+{
+       struct pending_cmd *cmd;
+
+       list_for_each_entry(cmd, &hdev->mgmt_pending, list) {
+               if (cmd->opcode == opcode)
+                       return cmd;
+       }
+
+       return NULL;
+}
+
 static u8 create_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
 {
        u8 ad_len = 0;
@@ -568,7 +580,7 @@ static void update_scan_rsp_data(struct hci_request *req)
        struct hci_cp_le_set_scan_rsp_data cp;
        u8 len;
 
-       if (!lmp_le_capable(hdev))
+       if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
                return;
 
        memset(&cp, 0, sizeof(cp));
@@ -587,12 +599,35 @@ static void update_scan_rsp_data(struct hci_request *req)
        hci_req_add(req, HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(cp), &cp);
 }
 
+static u8 get_adv_discov_flags(struct hci_dev *hdev)
+{
+       struct pending_cmd *cmd;
+
+       /* If there's a pending mgmt command the flags will not yet have
+        * their final values, so check for this first.
+        */
+       cmd = mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, hdev);
+       if (cmd) {
+               struct mgmt_mode *cp = cmd->param;
+               if (cp->val == 0x01)
+                       return LE_AD_GENERAL;
+               else if (cp->val == 0x02)
+                       return LE_AD_LIMITED;
+       } else {
+               if (test_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags))
+                       return LE_AD_LIMITED;
+               else if (test_bit(HCI_DISCOVERABLE, &hdev->dev_flags))
+                       return LE_AD_GENERAL;
+       }
+
+       return 0;
+}
+
 static u8 create_adv_data(struct hci_dev *hdev, u8 *ptr)
 {
        u8 ad_len = 0, flags = 0;
 
-       if (test_bit(HCI_ADVERTISING, &hdev->dev_flags))
-               flags |= LE_AD_GENERAL;
+       flags |= get_adv_discov_flags(hdev);
 
        if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
                if (lmp_le_br_capable(hdev))
@@ -632,7 +667,7 @@ static void update_adv_data(struct hci_request *req)
        struct hci_cp_le_set_adv_data cp;
        u8 len;
 
-       if (!lmp_le_capable(hdev))
+       if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
                return;
 
        memset(&cp, 0, sizeof(cp));
@@ -749,6 +784,9 @@ static void update_class(struct hci_request *req)
        if (!hdev_is_powered(hdev))
                return;
 
+       if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
+               return;
+
        if (test_bit(HCI_SERVICE_CACHE, &hdev->dev_flags))
                return;
 
@@ -883,18 +921,6 @@ static void mgmt_pending_foreach(u16 opcode, struct hci_dev *hdev,
        }
 }
 
-static struct pending_cmd *mgmt_pending_find(u16 opcode, struct hci_dev *hdev)
-{
-       struct pending_cmd *cmd;
-
-       list_for_each_entry(cmd, &hdev->mgmt_pending, list) {
-               if (cmd->opcode == opcode)
-                       return cmd;
-       }
-
-       return NULL;
-}
-
 static void mgmt_pending_remove(struct pending_cmd *cmd)
 {
        list_del(&cmd->list);
@@ -1117,15 +1143,15 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
        struct pending_cmd *cmd;
        struct hci_request req;
        u16 timeout;
-       u8 scan, status;
+       u8 scan;
        int err;
 
        BT_DBG("request for %s", hdev->name);
 
-       status = mgmt_bredr_support(hdev);
-       if (status)
+       if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags) &&
+           !test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
                return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
-                                 status);
+                                 MGMT_STATUS_REJECTED);
 
        if (cp->val != 0x00 && cp->val != 0x01 && cp->val != 0x02)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
@@ -1217,8 +1243,20 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
        cancel_delayed_work(&hdev->discov_off);
        hdev->discov_timeout = timeout;
 
+       /* Limited discoverable mode */
+       if (cp->val == 0x02)
+               set_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
+       else
+               clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
+
        hci_req_init(&req, hdev);
 
+       /* The procedure for LE-only controllers is much simpler - just
+        * update the advertising data.
+        */
+       if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
+               goto update_ad;
+
        scan = SCAN_PAGE;
 
        if (cp->val) {
@@ -1226,9 +1264,7 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
 
                if (cp->val == 0x02) {
                        /* Limited discoverable mode */
-                       set_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
-
-                       hci_cp.num_iac = 2;
+                       hci_cp.num_iac = min_t(u8, hdev->num_iac, 2);
                        hci_cp.iac_lap[0] = 0x00;       /* LIAC */
                        hci_cp.iac_lap[1] = 0x8b;
                        hci_cp.iac_lap[2] = 0x9e;
@@ -1237,8 +1273,6 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
                        hci_cp.iac_lap[5] = 0x9e;
                } else {
                        /* General discoverable mode */
-                       clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
-
                        hci_cp.num_iac = 1;
                        hci_cp.iac_lap[0] = 0x33;       /* GIAC */
                        hci_cp.iac_lap[1] = 0x8b;
@@ -1255,6 +1289,9 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
 
        hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan);
 
+update_ad:
+       update_adv_data(&req);
+
        err = hci_req_run(&req, set_discoverable_complete);
        if (err < 0)
                mgmt_pending_remove(cmd);
@@ -1270,6 +1307,9 @@ static void write_fast_connectable(struct hci_request *req, bool enable)
        struct hci_cp_write_page_scan_activity acp;
        u8 type;
 
+       if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
+               return;
+
        if (hdev->hci_ver < BLUETOOTH_VER_1_2)
                return;
 
@@ -1378,6 +1418,32 @@ unlock:
        hci_dev_unlock(hdev);
 }
 
+static int set_connectable_update_settings(struct hci_dev *hdev,
+                                          struct sock *sk, u8 val)
+{
+       bool changed = false;
+       int err;
+
+       if (!!val != test_bit(HCI_CONNECTABLE, &hdev->dev_flags))
+               changed = true;
+
+       if (val) {
+               set_bit(HCI_CONNECTABLE, &hdev->dev_flags);
+       } else {
+               clear_bit(HCI_CONNECTABLE, &hdev->dev_flags);
+               clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
+       }
+
+       err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE, hdev);
+       if (err < 0)
+               return err;
+
+       if (changed)
+               return new_settings(hdev, sk);
+
+       return 0;
+}
+
 static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data,
                           u16 len)
 {
@@ -1401,25 +1467,7 @@ static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data,
        hci_dev_lock(hdev);
 
        if (!hdev_is_powered(hdev)) {
-               bool changed = false;
-
-               if (!!cp->val != test_bit(HCI_CONNECTABLE, &hdev->dev_flags))
-                       changed = true;
-
-               if (cp->val) {
-                       set_bit(HCI_CONNECTABLE, &hdev->dev_flags);
-               } else {
-                       clear_bit(HCI_CONNECTABLE, &hdev->dev_flags);
-                       clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
-               }
-
-               err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE, hdev);
-               if (err < 0)
-                       goto failed;
-
-               if (changed)
-                       err = new_settings(hdev, sk);
-
+               err = set_connectable_update_settings(hdev, sk, cp->val);
                goto failed;
        }
 
@@ -1438,8 +1486,17 @@ static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data,
 
        hci_req_init(&req, hdev);
 
-       if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags) &&
-           cp->val != test_bit(HCI_PSCAN, &hdev->flags)) {
+       /* If BR/EDR is not enabled and we disable advertising as a
+        * by-product of disabling connectable, we need to update the
+        * advertising flags.
+        */
+       if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
+               if (!cp->val) {
+                       clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
+                       clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
+               }
+               update_adv_data(&req);
+       } else if (cp->val != test_bit(HCI_PSCAN, &hdev->flags)) {
                if (cp->val) {
                        scan = SCAN_PAGE;
                } else {
@@ -1472,8 +1529,8 @@ static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data,
        if (err < 0) {
                mgmt_pending_remove(cmd);
                if (err == -ENODATA)
-                       err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE,
-                                               hdev);
+                       err = set_connectable_update_settings(hdev, sk,
+                                                             cp->val);
                goto failed;
        }
 
@@ -4326,7 +4383,6 @@ void mgmt_set_powered_failed(struct hci_dev *hdev, int err)
 void mgmt_discoverable_timeout(struct hci_dev *hdev)
 {
        struct hci_request req;
-       u8 scan = SCAN_PAGE;
 
        hci_dev_lock(hdev);
 
@@ -4336,14 +4392,22 @@ void mgmt_discoverable_timeout(struct hci_dev *hdev)
         * safe to unconditionally clear the flag.
         */
        clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
+       clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
 
        hci_req_init(&req, hdev);
-       hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan);
+       if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
+               u8 scan = SCAN_PAGE;
+               hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE,
+                           sizeof(scan), &scan);
+       }
        update_class(&req);
+       update_adv_data(&req);
        hci_req_run(&req, NULL);
 
        hdev->discov_timeout = 0;
 
+       new_settings(hdev, NULL);
+
        hci_dev_unlock(hdev);
 }
 
@@ -4358,13 +4422,26 @@ void mgmt_discoverable(struct hci_dev *hdev, u8 discoverable)
        if (mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, hdev))
                return;
 
-       if (discoverable)
+       if (discoverable) {
                changed = !test_and_set_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
-       else
+       } else {
+               clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
                changed = test_and_clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
+       }
+
+       if (changed) {
+               struct hci_request req;
+
+               /* In case this change in discoverable was triggered by
+                * a disabling of connectable there could be a need to
+                * update the advertising flags.
+                */
+               hci_req_init(&req, hdev);
+               update_adv_data(&req);
+               hci_req_run(&req, NULL);
 
-       if (changed)
                new_settings(hdev, NULL);
+       }
 }
 
 void mgmt_connectable(struct hci_dev *hdev, u8 connectable)