]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/bluetooth/mgmt.c
Bluetooth: Fix limited discoverable mode for Zeevo modules
[karo-tx-linux.git] / net / bluetooth / mgmt.c
index 90d9353734496bd97727b48880c90f65170c40e3..22cf54710744803506b266ab4d9af75611492ecb 100644 (file)
@@ -536,13 +536,98 @@ static u8 *create_uuid128_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
        return ptr;
 }
 
-static u8 create_ad(struct hci_dev *hdev, u8 *ptr)
+static struct pending_cmd *mgmt_pending_find(u16 opcode, struct hci_dev *hdev)
 {
-       u8 ad_len = 0, flags = 0;
+       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;
        size_t name_len;
 
-       if (test_bit(HCI_ADVERTISING, &hdev->dev_flags))
-               flags |= LE_AD_GENERAL;
+       name_len = strlen(hdev->dev_name);
+       if (name_len > 0) {
+               size_t max_len = HCI_MAX_AD_LENGTH - ad_len - 2;
+
+               if (name_len > max_len) {
+                       name_len = max_len;
+                       ptr[1] = EIR_NAME_SHORT;
+               } else
+                       ptr[1] = EIR_NAME_COMPLETE;
+
+               ptr[0] = name_len + 1;
+
+               memcpy(ptr + 2, hdev->dev_name, name_len);
+
+               ad_len += (name_len + 2);
+               ptr += (name_len + 2);
+       }
+
+       return ad_len;
+}
+
+static void update_scan_rsp_data(struct hci_request *req)
+{
+       struct hci_dev *hdev = req->hdev;
+       struct hci_cp_le_set_scan_rsp_data cp;
+       u8 len;
+
+       if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
+               return;
+
+       memset(&cp, 0, sizeof(cp));
+
+       len = create_scan_rsp_data(hdev, cp.data);
+
+       if (hdev->scan_rsp_data_len == len &&
+           memcmp(cp.data, hdev->scan_rsp_data, len) == 0)
+               return;
+
+       memcpy(hdev->scan_rsp_data, cp.data, sizeof(cp.data));
+       hdev->scan_rsp_data_len = len;
+
+       cp.length = len;
+
+       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;
+
+       flags |= get_adv_discov_flags(hdev);
 
        if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
                if (lmp_le_br_capable(hdev))
@@ -573,39 +658,21 @@ static u8 create_ad(struct hci_dev *hdev, u8 *ptr)
                ptr += 3;
        }
 
-       name_len = strlen(hdev->dev_name);
-       if (name_len > 0) {
-               size_t max_len = HCI_MAX_AD_LENGTH - ad_len - 2;
-
-               if (name_len > max_len) {
-                       name_len = max_len;
-                       ptr[1] = EIR_NAME_SHORT;
-               } else
-                       ptr[1] = EIR_NAME_COMPLETE;
-
-               ptr[0] = name_len + 1;
-
-               memcpy(ptr + 2, hdev->dev_name, name_len);
-
-               ad_len += (name_len + 2);
-               ptr += (name_len + 2);
-       }
-
        return ad_len;
 }
 
-static void update_ad(struct hci_request *req)
+static void update_adv_data(struct hci_request *req)
 {
        struct hci_dev *hdev = req->hdev;
        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));
 
-       len = create_ad(hdev, cp.data);
+       len = create_adv_data(hdev, cp.data);
 
        if (hdev->adv_data_len == len &&
            memcmp(cp.data, hdev->adv_data, len) == 0)
@@ -717,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;
 
@@ -851,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);
@@ -1085,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,
@@ -1185,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) {
@@ -1194,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;
@@ -1205,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;
@@ -1223,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);
@@ -1238,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;
 
@@ -1293,10 +1365,7 @@ static void enable_advertising(struct hci_request *req)
        cp.min_interval = __constant_cpu_to_le16(0x0800);
        cp.max_interval = __constant_cpu_to_le16(0x0800);
        cp.type = get_adv_type(hdev);
-       if (bacmp(&hdev->bdaddr, BDADDR_ANY))
-               cp.own_address_type = ADDR_LE_DEV_PUBLIC;
-       else
-               cp.own_address_type = ADDR_LE_DEV_RANDOM;
+       cp.own_address_type = hdev->own_addr_type;
        cp.channel_map = 0x07;
 
        hci_req_add(req, HCI_OP_LE_SET_ADV_PARAM, sizeof(cp), &cp);
@@ -1349,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)
 {
@@ -1372,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;
        }
 
@@ -1409,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 {
@@ -1443,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;
        }
 
@@ -1714,7 +1800,8 @@ static void le_enable_complete(struct hci_dev *hdev, u8 status)
                hci_dev_lock(hdev);
 
                hci_req_init(&req, hdev);
-               update_ad(&req);
+               update_adv_data(&req);
+               update_scan_rsp_data(&req);
                hci_req_run(&req, NULL);
 
                hci_dev_unlock(hdev);
@@ -2935,8 +3022,11 @@ static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data,
                update_eir(&req);
        }
 
+       /* The name is stored in the scan response data and so
+        * no need to udpate the advertising data here.
+        */
        if (lmp_le_capable(hdev))
-               update_ad(&req);
+               update_scan_rsp_data(&req);
 
        err = hci_req_run(&req, set_name_complete);
        if (err < 0)
@@ -3201,10 +3291,7 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
                param_cp.type = LE_SCAN_ACTIVE;
                param_cp.interval = cpu_to_le16(DISCOV_LE_SCAN_INT);
                param_cp.window = cpu_to_le16(DISCOV_LE_SCAN_WIN);
-               if (bacmp(&hdev->bdaddr, BDADDR_ANY))
-                       param_cp.own_address_type = ADDR_LE_DEV_PUBLIC;
-               else
-                       param_cp.own_address_type = ADDR_LE_DEV_RANDOM;
+               param_cp.own_address_type = hdev->own_addr_type;
                hci_req_add(&req, HCI_OP_LE_SET_SCAN_PARAM, sizeof(param_cp),
                            &param_cp);
 
@@ -3888,7 +3975,7 @@ static int set_bredr(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
                goto unlock;
        }
 
-       /* We need to flip the bit already here so that update_ad
+       /* We need to flip the bit already here so that update_adv_data
         * generates the correct flags.
         */
        set_bit(HCI_BREDR_ENABLED, &hdev->dev_flags);
@@ -3898,7 +3985,10 @@ static int set_bredr(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
        if (test_bit(HCI_CONNECTABLE, &hdev->dev_flags))
                set_bredr_scan(&req);
 
-       update_ad(&req);
+       /* Since only the advertising data flags will change, there
+        * is no need to update the scan response data.
+        */
+       update_adv_data(&req);
 
        err = hci_req_run(&req, set_bredr_complete);
        if (err < 0)
@@ -4211,8 +4301,10 @@ static int powered_update_hci(struct hci_dev *hdev)
                 * advertising data. This also applies to the case
                 * where BR/EDR was toggled during the AUTO_OFF phase.
                 */
-               if (test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
-                       update_ad(&req);
+               if (test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) {
+                       update_adv_data(&req);
+                       update_scan_rsp_data(&req);
+               }
 
                if (test_bit(HCI_ADVERTISING, &hdev->dev_flags))
                        enable_advertising(&req);
@@ -4291,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);
 
@@ -4301,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);
 }
 
@@ -4323,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)