]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/bluetooth/mgmt.c
Merge remote-tracking branch 'net-next/master'
[karo-tx-linux.git] / net / bluetooth / mgmt.c
index 1b5b10fab54552d54d98df7ddb76fe5a9240422e..16125ff918f10991f7633921f2d4cbb03861c8f1 100644 (file)
 #include <net/bluetooth/mgmt.h>
 #include <net/bluetooth/smp.h>
 
-bool enable_hs;
-
 #define MGMT_VERSION   1
-#define MGMT_REVISION  3
+#define MGMT_REVISION  4
 
 static const u16 mgmt_commands[] = {
        MGMT_OP_READ_INDEX_LIST,
@@ -77,6 +75,8 @@ static const u16 mgmt_commands[] = {
        MGMT_OP_UNBLOCK_DEVICE,
        MGMT_OP_SET_DEVICE_ID,
        MGMT_OP_SET_ADVERTISING,
+       MGMT_OP_SET_BREDR,
+       MGMT_OP_SET_STATIC_ADDRESS,
 };
 
 static const u16 mgmt_events[] = {
@@ -380,10 +380,8 @@ static u32 get_supported_settings(struct hci_dev *hdev)
                settings |= MGMT_SETTING_DISCOVERABLE;
                settings |= MGMT_SETTING_BREDR;
                settings |= MGMT_SETTING_LINK_SECURITY;
-       }
-
-       if (enable_hs)
                settings |= MGMT_SETTING_HS;
+       }
 
        if (lmp_le_capable(hdev)) {
                settings |= MGMT_SETTING_LE;
@@ -412,7 +410,7 @@ static u32 get_current_settings(struct hci_dev *hdev)
        if (test_bit(HCI_PAIRABLE, &hdev->dev_flags))
                settings |= MGMT_SETTING_PAIRABLE;
 
-       if (lmp_bredr_capable(hdev))
+       if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
                settings |= MGMT_SETTING_BREDR;
 
        if (test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
@@ -922,20 +920,41 @@ static void cmd_status_rsp(struct pending_cmd *cmd, void *data)
        mgmt_pending_remove(cmd);
 }
 
+static u8 mgmt_bredr_support(struct hci_dev *hdev)
+{
+       if (!lmp_bredr_capable(hdev))
+               return MGMT_STATUS_NOT_SUPPORTED;
+       else if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
+               return MGMT_STATUS_REJECTED;
+       else
+               return MGMT_STATUS_SUCCESS;
+}
+
+static u8 mgmt_le_support(struct hci_dev *hdev)
+{
+       if (!lmp_le_capable(hdev))
+               return MGMT_STATUS_NOT_SUPPORTED;
+       else if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
+               return MGMT_STATUS_REJECTED;
+       else
+               return MGMT_STATUS_SUCCESS;
+}
+
 static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
                            u16 len)
 {
        struct mgmt_cp_set_discoverable *cp = data;
        struct pending_cmd *cmd;
        u16 timeout;
-       u8 scan;
+       u8 scan, status;
        int err;
 
        BT_DBG("request for %s", hdev->name);
 
-       if (!lmp_bredr_capable(hdev))
+       status = mgmt_bredr_support(hdev);
+       if (status)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
-                                MGMT_STATUS_NOT_SUPPORTED);
+                                 status);
 
        if (cp->val != 0x00 && cp->val != 0x01)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
@@ -1084,14 +1103,15 @@ static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data,
        struct mgmt_mode *cp = data;
        struct pending_cmd *cmd;
        struct hci_request req;
-       u8 scan;
+       u8 scan, status;
        int err;
 
        BT_DBG("request for %s", hdev->name);
 
-       if (!lmp_bredr_capable(hdev))
+       status = mgmt_bredr_support(hdev);
+       if (status)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_CONNECTABLE,
-                                 MGMT_STATUS_NOT_SUPPORTED);
+                                 status);
 
        if (cp->val != 0x00 && cp->val != 0x01)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_CONNECTABLE,
@@ -1207,14 +1227,15 @@ static int set_link_security(struct sock *sk, struct hci_dev *hdev, void *data,
 {
        struct mgmt_mode *cp = data;
        struct pending_cmd *cmd;
-       u8 val;
+       u8 val, status;
        int err;
 
        BT_DBG("request for %s", hdev->name);
 
-       if (!lmp_bredr_capable(hdev))
+       status = mgmt_bredr_support(hdev);
+       if (status)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_LINK_SECURITY,
-                                 MGMT_STATUS_NOT_SUPPORTED);
+                                 status);
 
        if (cp->val != 0x00 && cp->val != 0x01)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_LINK_SECURITY,
@@ -1275,11 +1296,15 @@ static int set_ssp(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
 {
        struct mgmt_mode *cp = data;
        struct pending_cmd *cmd;
-       u8 val;
+       u8 val, status;
        int err;
 
        BT_DBG("request for %s", hdev->name);
 
+       status = mgmt_bredr_support(hdev);
+       if (status)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_SSP, status);
+
        if (!lmp_ssp_capable(hdev))
                return cmd_status(sk, hdev->id, MGMT_OP_SET_SSP,
                                  MGMT_STATUS_NOT_SUPPORTED);
@@ -1341,23 +1366,44 @@ failed:
 static int set_hs(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
 {
        struct mgmt_mode *cp = data;
+       bool changed;
+       u8 status;
+       int err;
 
        BT_DBG("request for %s", hdev->name);
 
-       if (!enable_hs)
-               return cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
-                                 MGMT_STATUS_NOT_SUPPORTED);
+       status = mgmt_bredr_support(hdev);
+       if (status)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_HS, status);
 
        if (cp->val != 0x00 && cp->val != 0x01)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
                                  MGMT_STATUS_INVALID_PARAMS);
 
-       if (cp->val)
-               set_bit(HCI_HS_ENABLED, &hdev->dev_flags);
-       else
-               clear_bit(HCI_HS_ENABLED, &hdev->dev_flags);
+       hci_dev_lock(hdev);
+
+       if (cp->val) {
+               changed = !test_and_set_bit(HCI_HS_ENABLED, &hdev->dev_flags);
+       } else {
+               if (hdev_is_powered(hdev)) {
+                       err = cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
+                                        MGMT_STATUS_REJECTED);
+                       goto unlock;
+               }
+
+               changed = test_and_clear_bit(HCI_HS_ENABLED, &hdev->dev_flags);
+       }
+
+       err = send_settings_rsp(sk, MGMT_OP_SET_HS, hdev);
+       if (err < 0)
+               goto unlock;
+
+       if (changed)
+               err = new_settings(hdev, sk);
 
-       return send_settings_rsp(sk, MGMT_OP_SET_HS, hdev);
+unlock:
+       hci_dev_unlock(hdev);
+       return err;
 }
 
 static void le_enable_complete(struct hci_dev *hdev, u8 status)
@@ -1400,7 +1446,7 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
                                  MGMT_STATUS_INVALID_PARAMS);
 
        /* LE-only devices do not allow toggling LE on/off */
-       if (!lmp_bredr_capable(hdev))
+       if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
                return cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
                                  MGMT_STATUS_REJECTED);
 
@@ -1779,6 +1825,12 @@ static int load_link_keys(struct sock *sk, struct hci_dev *hdev, void *data,
        u16 key_count, expected_len;
        int i;
 
+       BT_DBG("request for %s", hdev->name);
+
+       if (!lmp_bredr_capable(hdev))
+               return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LINK_KEYS,
+                                 MGMT_STATUS_NOT_SUPPORTED);
+
        key_count = __le16_to_cpu(cp->key_count);
 
        expected_len = sizeof(*cp) + key_count *
@@ -2758,6 +2810,7 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
        struct hci_request req;
        /* General inquiry access code (GIAC) */
        u8 lap[3] = { 0x33, 0x8b, 0x9e };
+       u8 status;
        int err;
 
        BT_DBG("%s", hdev->name);
@@ -2794,9 +2847,10 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
 
        switch (hdev->discovery.type) {
        case DISCOV_TYPE_BREDR:
-               if (!lmp_bredr_capable(hdev)) {
+               status = mgmt_bredr_support(hdev);
+               if (status) {
                        err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
-                                        MGMT_STATUS_NOT_SUPPORTED);
+                                        status);
                        mgmt_pending_remove(cmd);
                        goto failed;
                }
@@ -2818,15 +2872,16 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
 
        case DISCOV_TYPE_LE:
        case DISCOV_TYPE_INTERLEAVED:
-               if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) {
+               status = mgmt_le_support(hdev);
+               if (status) {
                        err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
-                                        MGMT_STATUS_NOT_SUPPORTED);
+                                        status);
                        mgmt_pending_remove(cmd);
                        goto failed;
                }
 
                if (hdev->discovery.type == DISCOV_TYPE_INTERLEAVED &&
-                   !lmp_bredr_capable(hdev)) {
+                   !test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
                        err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
                                         MGMT_STATUS_NOT_SUPPORTED);
                        mgmt_pending_remove(cmd);
@@ -3164,18 +3219,15 @@ static int set_advertising(struct sock *sk, struct hci_dev *hdev, void *data, u1
        struct mgmt_mode *cp = data;
        struct pending_cmd *cmd;
        struct hci_request req;
-       u8 val, enabled;
+       u8 val, enabled, status;
        int err;
 
        BT_DBG("request for %s", hdev->name);
 
-       if (!lmp_le_capable(hdev))
-               return cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
-                                 MGMT_STATUS_NOT_SUPPORTED);
-
-       if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
+       status = mgmt_le_support(hdev);
+       if (status)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
-                                 MGMT_STATUS_REJECTED);
+                                 status);
 
        if (cp->val != 0x00 && cp->val != 0x01)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
@@ -3230,6 +3282,46 @@ unlock:
        return err;
 }
 
+static int set_static_address(struct sock *sk, struct hci_dev *hdev,
+                             void *data, u16 len)
+{
+       struct mgmt_cp_set_static_address *cp = data;
+       int err;
+
+       BT_DBG("%s", hdev->name);
+
+       if (!lmp_le_capable(hdev))
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_STATIC_ADDRESS,
+                                 MGMT_STATUS_NOT_SUPPORTED);
+
+       if (hdev_is_powered(hdev))
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_STATIC_ADDRESS,
+                                 MGMT_STATUS_REJECTED);
+
+       if (bacmp(&cp->bdaddr, BDADDR_ANY)) {
+               if (!bacmp(&cp->bdaddr, BDADDR_NONE))
+                       return cmd_status(sk, hdev->id,
+                                         MGMT_OP_SET_STATIC_ADDRESS,
+                                         MGMT_STATUS_INVALID_PARAMS);
+
+               /* Two most significant bits shall be set */
+               if ((cp->bdaddr.b[5] & 0xc0) != 0xc0)
+                       return cmd_status(sk, hdev->id,
+                                         MGMT_OP_SET_STATIC_ADDRESS,
+                                         MGMT_STATUS_INVALID_PARAMS);
+       }
+
+       hci_dev_lock(hdev);
+
+       bacpy(&hdev->static_addr, &cp->bdaddr);
+
+       err = cmd_complete(sk, hdev->id, MGMT_OP_SET_STATIC_ADDRESS, 0, NULL, 0);
+
+       hci_dev_unlock(hdev);
+
+       return err;
+}
+
 static void fast_connectable_complete(struct hci_dev *hdev, u8 status)
 {
        struct pending_cmd *cmd;
@@ -3273,7 +3365,8 @@ static int set_fast_connectable(struct sock *sk, struct hci_dev *hdev,
 
        BT_DBG("%s", hdev->name);
 
-       if (!lmp_bredr_capable(hdev) || hdev->hci_ver < BLUETOOTH_VER_1_2)
+       if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags) ||
+           hdev->hci_ver < BLUETOOTH_VER_1_2)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE,
                                  MGMT_STATUS_NOT_SUPPORTED);
 
@@ -3327,6 +3420,121 @@ unlock:
        return err;
 }
 
+static void set_bredr_complete(struct hci_dev *hdev, u8 status)
+{
+       struct pending_cmd *cmd;
+
+       BT_DBG("status 0x%02x", status);
+
+       hci_dev_lock(hdev);
+
+       cmd = mgmt_pending_find(MGMT_OP_SET_BREDR, hdev);
+       if (!cmd)
+               goto unlock;
+
+       if (status) {
+               u8 mgmt_err = mgmt_status(status);
+
+               /* We need to restore the flag if related HCI commands
+                * failed.
+                */
+               clear_bit(HCI_BREDR_ENABLED, &hdev->dev_flags);
+
+               cmd_status(cmd->sk, cmd->index, cmd->opcode, mgmt_err);
+       } else {
+               send_settings_rsp(cmd->sk, MGMT_OP_SET_BREDR, hdev);
+               new_settings(hdev, cmd->sk);
+       }
+
+       mgmt_pending_remove(cmd);
+
+unlock:
+       hci_dev_unlock(hdev);
+}
+
+static int set_bredr(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
+{
+       struct mgmt_mode *cp = data;
+       struct pending_cmd *cmd;
+       struct hci_request req;
+       int err;
+
+       BT_DBG("request for %s", hdev->name);
+
+       if (!lmp_bredr_capable(hdev) || !lmp_le_capable(hdev))
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
+                                 MGMT_STATUS_NOT_SUPPORTED);
+
+       if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
+                                 MGMT_STATUS_REJECTED);
+
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
+       hci_dev_lock(hdev);
+
+       if (cp->val == test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
+               err = send_settings_rsp(sk, MGMT_OP_SET_BREDR, hdev);
+               goto unlock;
+       }
+
+       if (!hdev_is_powered(hdev)) {
+               if (!cp->val) {
+                       clear_bit(HCI_CONNECTABLE, &hdev->dev_flags);
+                       clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
+                       clear_bit(HCI_SSP_ENABLED, &hdev->dev_flags);
+                       clear_bit(HCI_LINK_SECURITY, &hdev->dev_flags);
+                       clear_bit(HCI_FAST_CONNECTABLE, &hdev->dev_flags);
+                       clear_bit(HCI_HS_ENABLED, &hdev->dev_flags);
+               }
+
+               change_bit(HCI_BREDR_ENABLED, &hdev->dev_flags);
+
+               err = send_settings_rsp(sk, MGMT_OP_SET_BREDR, hdev);
+               if (err < 0)
+                       goto unlock;
+
+               err = new_settings(hdev, sk);
+               goto unlock;
+       }
+
+       /* Reject disabling when powered on */
+       if (!cp->val) {
+               err = cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
+                                MGMT_STATUS_REJECTED);
+               goto unlock;
+       }
+
+       if (mgmt_pending_find(MGMT_OP_SET_BREDR, hdev)) {
+               err = cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
+                                MGMT_STATUS_BUSY);
+               goto unlock;
+       }
+
+       cmd = mgmt_pending_add(sk, MGMT_OP_SET_BREDR, hdev, data, len);
+       if (!cmd) {
+               err = -ENOMEM;
+               goto unlock;
+       }
+
+       /* We need to flip the bit already here so that hci_update_ad
+        * generates the correct flags.
+        */
+       set_bit(HCI_BREDR_ENABLED, &hdev->dev_flags);
+
+       hci_req_init(&req, hdev);
+       hci_update_ad(&req);
+       err = hci_req_run(&req, set_bredr_complete);
+       if (err < 0)
+               mgmt_pending_remove(cmd);
+
+unlock:
+       hci_dev_unlock(hdev);
+       return err;
+}
+
 static bool ltk_is_valid(struct mgmt_ltk_info *key)
 {
        if (key->authenticated != 0x00 && key->authenticated != 0x01)
@@ -3345,6 +3553,12 @@ static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
        u16 key_count, expected_len;
        int i, err;
 
+       BT_DBG("request for %s", hdev->name);
+
+       if (!lmp_le_capable(hdev))
+               return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS,
+                                 MGMT_STATUS_NOT_SUPPORTED);
+
        key_count = __le16_to_cpu(cp->key_count);
 
        expected_len = sizeof(*cp) + key_count *
@@ -3442,6 +3656,8 @@ static const struct mgmt_handler {
        { unblock_device,         false, MGMT_UNBLOCK_DEVICE_SIZE },
        { set_device_id,          false, MGMT_SET_DEVICE_ID_SIZE },
        { set_advertising,        false, MGMT_SETTING_SIZE },
+       { set_bredr,              false, MGMT_SETTING_SIZE },
+       { set_static_address,     false, MGMT_SET_STATIC_ADDRESS_SIZE },
 };
 
 
@@ -3623,6 +3839,16 @@ static int powered_update_hci(struct hci_dev *hdev)
                    cp.simul != lmp_host_le_br_capable(hdev))
                        hci_req_add(&req, HCI_OP_WRITE_LE_HOST_SUPPORTED,
                                    sizeof(cp), &cp);
+
+               /* In case BR/EDR was toggled during the AUTO_OFF phase */
+               hci_update_ad(&req);
+       }
+
+       if (lmp_le_capable(hdev)) {
+               /* Set random address to static address if configured */
+               if (bacmp(&hdev->static_addr, BDADDR_ANY))
+                       hci_req_add(&req, HCI_OP_LE_SET_RANDOM_ADDR, 6,
+                                   &hdev->static_addr);
        }
 
        if (test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags)) {
@@ -3637,7 +3863,8 @@ static int powered_update_hci(struct hci_dev *hdev)
                            sizeof(link_sec), &link_sec);
 
        if (lmp_bredr_capable(hdev)) {
-               set_bredr_scan(&req);
+               if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
+                       set_bredr_scan(&req);
                update_class(&req);
                update_name(&req);
                update_eir(&req);
@@ -4396,6 +4623,3 @@ int mgmt_device_unblocked(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type)
        return mgmt_event(MGMT_EV_DEVICE_UNBLOCKED, hdev, &ev, sizeof(ev),
                          cmd ? cmd->sk : NULL);
 }
-
-module_param(enable_hs, bool, 0644);
-MODULE_PARM_DESC(enable_hs, "Enable High Speed support");