]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/bluetooth/mgmt.c
Merge tag 'arc-v3.9-rc1-late' of git://git.kernel.org/pub/scm/linux/kernel/git/vgupta/arc
[karo-tx-linux.git] / net / bluetooth / mgmt.c
index 28e01f9922311e988be61e208debfb726c1e93d0..39395c7144aa16d0402ae9a71ecd2cd248a1affa 100644 (file)
@@ -35,7 +35,7 @@
 bool enable_hs;
 
 #define MGMT_VERSION   1
-#define MGMT_REVISION  2
+#define MGMT_REVISION  3
 
 static const u16 mgmt_commands[] = {
        MGMT_OP_READ_INDEX_LIST,
@@ -435,35 +435,117 @@ static u32 get_current_settings(struct hci_dev *hdev)
 
 #define PNP_INFO_SVCLASS_ID            0x1200
 
-static u8 bluetooth_base_uuid[] = {
-                       0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
-                       0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
+static u8 *create_uuid16_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
+{
+       u8 *ptr = data, *uuids_start = NULL;
+       struct bt_uuid *uuid;
+
+       if (len < 4)
+               return ptr;
+
+       list_for_each_entry(uuid, &hdev->uuids, list) {
+               u16 uuid16;
+
+               if (uuid->size != 16)
+                       continue;
+
+               uuid16 = get_unaligned_le16(&uuid->uuid[12]);
+               if (uuid16 < 0x1100)
+                       continue;
+
+               if (uuid16 == PNP_INFO_SVCLASS_ID)
+                       continue;
+
+               if (!uuids_start) {
+                       uuids_start = ptr;
+                       uuids_start[0] = 1;
+                       uuids_start[1] = EIR_UUID16_ALL;
+                       ptr += 2;
+               }
+
+               /* Stop if not enough space to put next UUID */
+               if ((ptr - data) + sizeof(u16) > len) {
+                       uuids_start[1] = EIR_UUID16_SOME;
+                       break;
+               }
+
+               *ptr++ = (uuid16 & 0x00ff);
+               *ptr++ = (uuid16 & 0xff00) >> 8;
+               uuids_start[0] += sizeof(uuid16);
+       }
+
+       return ptr;
+}
 
-static u16 get_uuid16(u8 *uuid128)
+static u8 *create_uuid32_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
 {
-       u32 val;
-       int i;
+       u8 *ptr = data, *uuids_start = NULL;
+       struct bt_uuid *uuid;
 
-       for (i = 0; i < 12; i++) {
-               if (bluetooth_base_uuid[i] != uuid128[i])
-                       return 0;
+       if (len < 6)
+               return ptr;
+
+       list_for_each_entry(uuid, &hdev->uuids, list) {
+               if (uuid->size != 32)
+                       continue;
+
+               if (!uuids_start) {
+                       uuids_start = ptr;
+                       uuids_start[0] = 1;
+                       uuids_start[1] = EIR_UUID32_ALL;
+                       ptr += 2;
+               }
+
+               /* Stop if not enough space to put next UUID */
+               if ((ptr - data) + sizeof(u32) > len) {
+                       uuids_start[1] = EIR_UUID32_SOME;
+                       break;
+               }
+
+               memcpy(ptr, &uuid->uuid[12], sizeof(u32));
+               ptr += sizeof(u32);
+               uuids_start[0] += sizeof(u32);
        }
 
-       val = get_unaligned_le32(&uuid128[12]);
-       if (val > 0xffff)
-               return 0;
+       return ptr;
+}
+
+static u8 *create_uuid128_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len)
+{
+       u8 *ptr = data, *uuids_start = NULL;
+       struct bt_uuid *uuid;
+
+       if (len < 18)
+               return ptr;
+
+       list_for_each_entry(uuid, &hdev->uuids, list) {
+               if (uuid->size != 128)
+                       continue;
+
+               if (!uuids_start) {
+                       uuids_start = ptr;
+                       uuids_start[0] = 1;
+                       uuids_start[1] = EIR_UUID128_ALL;
+                       ptr += 2;
+               }
+
+               /* Stop if not enough space to put next UUID */
+               if ((ptr - data) + 16 > len) {
+                       uuids_start[1] = EIR_UUID128_SOME;
+                       break;
+               }
+
+               memcpy(ptr, uuid->uuid, 16);
+               ptr += 16;
+               uuids_start[0] += 16;
+       }
 
-       return (u16) val;
+       return ptr;
 }
 
 static void create_eir(struct hci_dev *hdev, u8 *data)
 {
        u8 *ptr = data;
-       u16 eir_len = 0;
-       u16 uuid16_list[HCI_MAX_EIR_LENGTH / sizeof(u16)];
-       int i, truncated = 0;
-       struct bt_uuid *uuid;
        size_t name_len;
 
        name_len = strlen(hdev->dev_name);
@@ -481,7 +563,6 @@ static void create_eir(struct hci_dev *hdev, u8 *data)
 
                memcpy(ptr + 2, hdev->dev_name, name_len);
 
-               eir_len += (name_len + 2);
                ptr += (name_len + 2);
        }
 
@@ -490,7 +571,6 @@ static void create_eir(struct hci_dev *hdev, u8 *data)
                ptr[1] = EIR_TX_POWER;
                ptr[2] = (u8) hdev->inq_tx_power;
 
-               eir_len += 3;
                ptr += 3;
        }
 
@@ -503,60 +583,12 @@ static void create_eir(struct hci_dev *hdev, u8 *data)
                put_unaligned_le16(hdev->devid_product, ptr + 6);
                put_unaligned_le16(hdev->devid_version, ptr + 8);
 
-               eir_len += 10;
                ptr += 10;
        }
 
-       memset(uuid16_list, 0, sizeof(uuid16_list));
-
-       /* Group all UUID16 types */
-       list_for_each_entry(uuid, &hdev->uuids, list) {
-               u16 uuid16;
-
-               uuid16 = get_uuid16(uuid->uuid);
-               if (uuid16 == 0)
-                       return;
-
-               if (uuid16 < 0x1100)
-                       continue;
-
-               if (uuid16 == PNP_INFO_SVCLASS_ID)
-                       continue;
-
-               /* Stop if not enough space to put next UUID */
-               if (eir_len + 2 + sizeof(u16) > HCI_MAX_EIR_LENGTH) {
-                       truncated = 1;
-                       break;
-               }
-
-               /* Check for duplicates */
-               for (i = 0; uuid16_list[i] != 0; i++)
-                       if (uuid16_list[i] == uuid16)
-                               break;
-
-               if (uuid16_list[i] == 0) {
-                       uuid16_list[i] = uuid16;
-                       eir_len += sizeof(u16);
-               }
-       }
-
-       if (uuid16_list[0] != 0) {
-               u8 *length = ptr;
-
-               /* EIR Data type */
-               ptr[1] = truncated ? EIR_UUID16_SOME : EIR_UUID16_ALL;
-
-               ptr += 2;
-               eir_len += 2;
-
-               for (i = 0; uuid16_list[i] != 0; i++) {
-                       *ptr++ = (uuid16_list[i] & 0x00ff);
-                       *ptr++ = (uuid16_list[i] & 0xff00) >> 8;
-               }
-
-               /* EIR Data length */
-               *length = (i * sizeof(u16)) + 1;
-       }
+       ptr = create_uuid16_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data));
+       ptr = create_uuid32_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data));
+       ptr = create_uuid128_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data));
 }
 
 static int update_eir(struct hci_dev *hdev)
@@ -728,13 +760,9 @@ static void mgmt_pending_foreach(u16 opcode, struct hci_dev *hdev,
                                            void *data),
                                 void *data)
 {
-       struct list_head *p, *n;
-
-       list_for_each_safe(p, n, &hdev->mgmt_pending) {
-               struct pending_cmd *cmd;
-
-               cmd = list_entry(p, struct pending_cmd, list);
+       struct pending_cmd *cmd, *tmp;
 
+       list_for_each_entry_safe(cmd, tmp, &hdev->mgmt_pending, list) {
                if (opcode > 0 && cmd->opcode != opcode)
                        continue;
 
@@ -777,14 +805,19 @@ static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data,
 
        BT_DBG("request for %s", hdev->name);
 
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_POWERED,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
        hci_dev_lock(hdev);
 
        if (test_and_clear_bit(HCI_AUTO_OFF, &hdev->dev_flags)) {
                cancel_delayed_work(&hdev->power_off);
 
                if (cp->val) {
-                       err = send_settings_rsp(sk, MGMT_OP_SET_POWERED, hdev);
-                       mgmt_powered(hdev, 1);
+                       mgmt_pending_add(sk, MGMT_OP_SET_POWERED, hdev,
+                                        data, len);
+                       err = mgmt_powered(hdev, 1);
                        goto failed;
                }
        }
@@ -807,9 +840,9 @@ static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data,
        }
 
        if (cp->val)
-               schedule_work(&hdev->power_on);
+               queue_work(hdev->req_workqueue, &hdev->power_on);
        else
-               schedule_work(&hdev->power_off.work);
+               queue_work(hdev->req_workqueue, &hdev->power_off.work);
 
        err = 0;
 
@@ -872,6 +905,10 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
                return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
                                 MGMT_STATUS_NOT_SUPPORTED);
 
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
        timeout = __le16_to_cpu(cp->timeout);
        if (!cp->val && timeout > 0)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
@@ -971,6 +1008,10 @@ static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data,
                return cmd_status(sk, hdev->id, MGMT_OP_SET_CONNECTABLE,
                                  MGMT_STATUS_NOT_SUPPORTED);
 
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_CONNECTABLE,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
        hci_dev_lock(hdev);
 
        if (!hdev_is_powered(hdev)) {
@@ -1041,6 +1082,10 @@ static int set_pairable(struct sock *sk, struct hci_dev *hdev, void *data,
 
        BT_DBG("request for %s", hdev->name);
 
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_PAIRABLE,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
        hci_dev_lock(hdev);
 
        if (cp->val)
@@ -1073,6 +1118,10 @@ static int set_link_security(struct sock *sk, struct hci_dev *hdev, void *data,
                return cmd_status(sk, hdev->id, MGMT_OP_SET_LINK_SECURITY,
                                  MGMT_STATUS_NOT_SUPPORTED);
 
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_LINK_SECURITY,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
        hci_dev_lock(hdev);
 
        if (!hdev_is_powered(hdev)) {
@@ -1137,6 +1186,10 @@ static int set_ssp(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_SSP,
                                  MGMT_STATUS_NOT_SUPPORTED);
 
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_SSP,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
        hci_dev_lock(hdev);
 
        val = !!cp->val;
@@ -1197,6 +1250,10 @@ static int set_hs(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
                                  MGMT_STATUS_NOT_SUPPORTED);
 
+       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
@@ -1219,6 +1276,10 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
                return cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
                                  MGMT_STATUS_NOT_SUPPORTED);
 
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
        hci_dev_lock(hdev);
 
        val = !!cp->val;
@@ -1271,6 +1332,25 @@ unlock:
        return err;
 }
 
+static const u8 bluetooth_base_uuid[] = {
+                       0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80,
+                       0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static u8 get_uuid_size(const u8 *uuid)
+{
+       u32 val;
+
+       if (memcmp(uuid, bluetooth_base_uuid, 12))
+               return 128;
+
+       val = get_unaligned_le32(&uuid[12]);
+       if (val > 0xffff)
+               return 32;
+
+       return 16;
+}
+
 static int add_uuid(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
 {
        struct mgmt_cp_add_uuid *cp = data;
@@ -1296,8 +1376,9 @@ static int add_uuid(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
 
        memcpy(uuid->uuid, cp->uuid, 16);
        uuid->svc_hint = cp->svc_hint;
+       uuid->size = get_uuid_size(cp->uuid);
 
-       list_add(&uuid->list, &hdev->uuids);
+       list_add_tail(&uuid->list, &hdev->uuids);
 
        err = update_class(hdev);
        if (err < 0)
@@ -1328,7 +1409,8 @@ static bool enable_service_cache(struct hci_dev *hdev)
                return false;
 
        if (!test_and_set_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) {
-               schedule_delayed_work(&hdev->service_cache, CACHE_TIMEOUT);
+               queue_delayed_work(hdev->workqueue, &hdev->service_cache,
+                                  CACHE_TIMEOUT);
                return true;
        }
 
@@ -1340,7 +1422,7 @@ static int remove_uuid(struct sock *sk, struct hci_dev *hdev, void *data,
 {
        struct mgmt_cp_remove_uuid *cp = data;
        struct pending_cmd *cmd;
-       struct list_head *p, *n;
+       struct bt_uuid *match, *tmp;
        u8 bt_uuid_any[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        int err, found;
 
@@ -1368,9 +1450,7 @@ static int remove_uuid(struct sock *sk, struct hci_dev *hdev, void *data,
 
        found = 0;
 
-       list_for_each_safe(p, n, &hdev->uuids) {
-               struct bt_uuid *match = list_entry(p, struct bt_uuid, list);
-
+       list_for_each_entry_safe(match, tmp, &hdev->uuids, list) {
                if (memcmp(match->uuid, cp->uuid, 16) != 0)
                        continue;
 
@@ -1485,9 +1565,21 @@ static int load_link_keys(struct sock *sk, struct hci_dev *hdev, void *data,
                                  MGMT_STATUS_INVALID_PARAMS);
        }
 
+       if (cp->debug_keys != 0x00 && cp->debug_keys != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LINK_KEYS,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
        BT_DBG("%s debug_keys %u key_count %u", hdev->name, cp->debug_keys,
               key_count);
 
+       for (i = 0; i < key_count; i++) {
+               struct mgmt_link_key_info *key = &cp->keys[i];
+
+               if (key->addr.type != BDADDR_BREDR)
+                       return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LINK_KEYS,
+                                         MGMT_STATUS_INVALID_PARAMS);
+       }
+
        hci_dev_lock(hdev);
 
        hci_link_keys_clear(hdev);
@@ -1535,12 +1627,22 @@ static int unpair_device(struct sock *sk, struct hci_dev *hdev, void *data,
        struct hci_conn *conn;
        int err;
 
-       hci_dev_lock(hdev);
-
        memset(&rp, 0, sizeof(rp));
        bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr);
        rp.addr.type = cp->addr.type;
 
+       if (!bdaddr_type_is_valid(cp->addr.type))
+               return cmd_complete(sk, hdev->id, MGMT_OP_UNPAIR_DEVICE,
+                                   MGMT_STATUS_INVALID_PARAMS,
+                                   &rp, sizeof(rp));
+
+       if (cp->disconnect != 0x00 && cp->disconnect != 0x01)
+               return cmd_complete(sk, hdev->id, MGMT_OP_UNPAIR_DEVICE,
+                                   MGMT_STATUS_INVALID_PARAMS,
+                                   &rp, sizeof(rp));
+
+       hci_dev_lock(hdev);
+
        if (!hdev_is_powered(hdev)) {
                err = cmd_complete(sk, hdev->id, MGMT_OP_UNPAIR_DEVICE,
                                   MGMT_STATUS_NOT_POWERED, &rp, sizeof(rp));
@@ -1598,6 +1700,7 @@ static int disconnect(struct sock *sk, struct hci_dev *hdev, void *data,
                      u16 len)
 {
        struct mgmt_cp_disconnect *cp = data;
+       struct mgmt_rp_disconnect rp;
        struct hci_cp_disconnect dc;
        struct pending_cmd *cmd;
        struct hci_conn *conn;
@@ -1605,17 +1708,26 @@ static int disconnect(struct sock *sk, struct hci_dev *hdev, void *data,
 
        BT_DBG("");
 
+       memset(&rp, 0, sizeof(rp));
+       bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr);
+       rp.addr.type = cp->addr.type;
+
+       if (!bdaddr_type_is_valid(cp->addr.type))
+               return cmd_complete(sk, hdev->id, MGMT_OP_DISCONNECT,
+                                   MGMT_STATUS_INVALID_PARAMS,
+                                   &rp, sizeof(rp));
+
        hci_dev_lock(hdev);
 
        if (!test_bit(HCI_UP, &hdev->flags)) {
-               err = cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT,
-                                MGMT_STATUS_NOT_POWERED);
+               err = cmd_complete(sk, hdev->id, MGMT_OP_DISCONNECT,
+                                  MGMT_STATUS_NOT_POWERED, &rp, sizeof(rp));
                goto failed;
        }
 
        if (mgmt_pending_find(MGMT_OP_DISCONNECT, hdev)) {
-               err = cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT,
-                                MGMT_STATUS_BUSY);
+               err = cmd_complete(sk, hdev->id, MGMT_OP_DISCONNECT,
+                                  MGMT_STATUS_BUSY, &rp, sizeof(rp));
                goto failed;
        }
 
@@ -1626,8 +1738,8 @@ static int disconnect(struct sock *sk, struct hci_dev *hdev, void *data,
                conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr);
 
        if (!conn || conn->state == BT_OPEN || conn->state == BT_CLOSED) {
-               err = cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT,
-                                MGMT_STATUS_NOT_CONNECTED);
+               err = cmd_complete(sk, hdev->id, MGMT_OP_DISCONNECT,
+                                  MGMT_STATUS_NOT_CONNECTED, &rp, sizeof(rp));
                goto failed;
        }
 
@@ -1905,11 +2017,20 @@ static int pair_device(struct sock *sk, struct hci_dev *hdev, void *data,
 
        BT_DBG("");
 
+       memset(&rp, 0, sizeof(rp));
+       bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr);
+       rp.addr.type = cp->addr.type;
+
+       if (!bdaddr_type_is_valid(cp->addr.type))
+               return cmd_complete(sk, hdev->id, MGMT_OP_PAIR_DEVICE,
+                                   MGMT_STATUS_INVALID_PARAMS,
+                                   &rp, sizeof(rp));
+
        hci_dev_lock(hdev);
 
        if (!hdev_is_powered(hdev)) {
-               err = cmd_status(sk, hdev->id, MGMT_OP_PAIR_DEVICE,
-                                MGMT_STATUS_NOT_POWERED);
+               err = cmd_complete(sk, hdev->id, MGMT_OP_PAIR_DEVICE,
+                                  MGMT_STATUS_NOT_POWERED, &rp, sizeof(rp));
                goto unlock;
        }
 
@@ -1926,10 +2047,6 @@ static int pair_device(struct sock *sk, struct hci_dev *hdev, void *data,
                conn = hci_connect(hdev, LE_LINK, &cp->addr.bdaddr,
                                   cp->addr.type, sec_level, auth_type);
 
-       memset(&rp, 0, sizeof(rp));
-       bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr);
-       rp.addr.type = cp->addr.type;
-
        if (IS_ERR(conn)) {
                int status;
 
@@ -2350,31 +2467,45 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
 
        switch (hdev->discovery.type) {
        case DISCOV_TYPE_BREDR:
-               if (lmp_bredr_capable(hdev))
-                       err = hci_do_inquiry(hdev, INQUIRY_LEN_BREDR);
-               else
-                       err = -ENOTSUPP;
+               if (!lmp_bredr_capable(hdev)) {
+                       err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
+                                        MGMT_STATUS_NOT_SUPPORTED);
+                       mgmt_pending_remove(cmd);
+                       goto failed;
+               }
+
+               err = hci_do_inquiry(hdev, INQUIRY_LEN_BREDR);
                break;
 
        case DISCOV_TYPE_LE:
-               if (lmp_host_le_capable(hdev))
-                       err = hci_le_scan(hdev, LE_SCAN_TYPE, LE_SCAN_INT,
-                                         LE_SCAN_WIN, LE_SCAN_TIMEOUT_LE_ONLY);
-               else
-                       err = -ENOTSUPP;
+               if (!lmp_host_le_capable(hdev)) {
+                       err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
+                                        MGMT_STATUS_NOT_SUPPORTED);
+                       mgmt_pending_remove(cmd);
+                       goto failed;
+               }
+
+               err = hci_le_scan(hdev, LE_SCAN_TYPE, LE_SCAN_INT,
+                                 LE_SCAN_WIN, LE_SCAN_TIMEOUT_LE_ONLY);
                break;
 
        case DISCOV_TYPE_INTERLEAVED:
-               if (lmp_host_le_capable(hdev) && lmp_bredr_capable(hdev))
-                       err = hci_le_scan(hdev, LE_SCAN_TYPE, LE_SCAN_INT,
-                                         LE_SCAN_WIN,
-                                         LE_SCAN_TIMEOUT_BREDR_LE);
-               else
-                       err = -ENOTSUPP;
+               if (!lmp_host_le_capable(hdev) || !lmp_bredr_capable(hdev)) {
+                       err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
+                                        MGMT_STATUS_NOT_SUPPORTED);
+                       mgmt_pending_remove(cmd);
+                       goto failed;
+               }
+
+               err = hci_le_scan(hdev, LE_SCAN_TYPE, LE_SCAN_INT, LE_SCAN_WIN,
+                                 LE_SCAN_TIMEOUT_BREDR_LE);
                break;
 
        default:
-               err = -EINVAL;
+               err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
+                                MGMT_STATUS_INVALID_PARAMS);
+               mgmt_pending_remove(cmd);
+               goto failed;
        }
 
        if (err < 0)
@@ -2512,6 +2643,11 @@ static int block_device(struct sock *sk, struct hci_dev *hdev, void *data,
 
        BT_DBG("%s", hdev->name);
 
+       if (!bdaddr_type_is_valid(cp->addr.type))
+               return cmd_complete(sk, hdev->id, MGMT_OP_BLOCK_DEVICE,
+                                   MGMT_STATUS_INVALID_PARAMS,
+                                   &cp->addr, sizeof(cp->addr));
+
        hci_dev_lock(hdev);
 
        err = hci_blacklist_add(hdev, &cp->addr.bdaddr, cp->addr.type);
@@ -2537,6 +2673,11 @@ static int unblock_device(struct sock *sk, struct hci_dev *hdev, void *data,
 
        BT_DBG("%s", hdev->name);
 
+       if (!bdaddr_type_is_valid(cp->addr.type))
+               return cmd_complete(sk, hdev->id, MGMT_OP_UNBLOCK_DEVICE,
+                                   MGMT_STATUS_INVALID_PARAMS,
+                                   &cp->addr, sizeof(cp->addr));
+
        hci_dev_lock(hdev);
 
        err = hci_blacklist_del(hdev, &cp->addr.bdaddr, cp->addr.type);
@@ -2598,6 +2739,10 @@ static int set_fast_connectable(struct sock *sk, struct hci_dev *hdev,
                return cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE,
                                  MGMT_STATUS_NOT_SUPPORTED);
 
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
        if (!hdev_is_powered(hdev))
                return cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE,
                                  MGMT_STATUS_NOT_POWERED);
@@ -2645,6 +2790,17 @@ done:
        return err;
 }
 
+static bool ltk_is_valid(struct mgmt_ltk_info *key)
+{
+       if (key->authenticated != 0x00 && key->authenticated != 0x01)
+               return false;
+       if (key->master != 0x00 && key->master != 0x01)
+               return false;
+       if (!bdaddr_type_is_le(key->addr.type))
+               return false;
+       return true;
+}
+
 static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
                               void *cp_data, u16 len)
 {
@@ -2660,11 +2816,20 @@ static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
                BT_ERR("load_keys: expected %u bytes, got %u bytes",
                       len, expected_len);
                return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS,
-                                 EINVAL);
+                                 MGMT_STATUS_INVALID_PARAMS);
        }
 
        BT_DBG("%s key_count %u", hdev->name, key_count);
 
+       for (i = 0; i < key_count; i++) {
+               struct mgmt_ltk_info *key = &cp->keys[i];
+
+               if (!ltk_is_valid(key))
+                       return cmd_status(sk, hdev->id,
+                                         MGMT_OP_LOAD_LONG_TERM_KEYS,
+                                         MGMT_STATUS_INVALID_PARAMS);
+       }
+
        hci_dev_lock(hdev);
 
        hci_smp_ltks_clear(hdev);
@@ -2904,6 +3069,8 @@ int mgmt_powered(struct hci_dev *hdev, u8 powered)
        mgmt_pending_foreach(MGMT_OP_SET_POWERED, hdev, settings_rsp, &match);
 
        if (powered) {
+               u8 link_sec;
+
                if (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags) &&
                    !lmp_host_ssp_capable(hdev)) {
                        u8 ssp = 1;
@@ -2927,6 +3094,11 @@ int mgmt_powered(struct hci_dev *hdev, u8 powered)
                                             sizeof(cp), &cp);
                }
 
+               link_sec = test_bit(HCI_LINK_SECURITY, &hdev->dev_flags);
+               if (link_sec != test_bit(HCI_AUTH, &hdev->flags))
+                       hci_send_cmd(hdev, HCI_OP_WRITE_AUTH_ENABLE,
+                                    sizeof(link_sec), &link_sec);
+
                if (lmp_bredr_capable(hdev)) {
                        set_bredr_scan(hdev);
                        update_class(hdev);
@@ -2935,7 +3107,13 @@ int mgmt_powered(struct hci_dev *hdev, u8 powered)
                }
        } else {
                u8 status = MGMT_STATUS_NOT_POWERED;
+               u8 zero_cod[] = { 0, 0, 0 };
+
                mgmt_pending_foreach(0, hdev, cmd_status_rsp, &status);
+
+               if (memcmp(hdev->dev_class, zero_cod, sizeof(zero_cod)) != 0)
+                       mgmt_event(MGMT_EV_CLASS_OF_DEV_CHANGED, hdev,
+                                  zero_cod, sizeof(zero_cod), NULL);
        }
 
        err = new_settings(hdev, match.sk);