Merge git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth
[pandora-kernel.git] / net / bluetooth / mgmt.c
index fedc539..1b5b10f 100644 (file)
@@ -76,6 +76,7 @@ static const u16 mgmt_commands[] = {
        MGMT_OP_BLOCK_DEVICE,
        MGMT_OP_UNBLOCK_DEVICE,
        MGMT_OP_SET_DEVICE_ID,
+       MGMT_OP_SET_ADVERTISING,
 };
 
 static const u16 mgmt_events[] = {
@@ -339,6 +340,9 @@ static int read_index_list(struct sock *sk, struct hci_dev *hdev, void *data,
                if (test_bit(HCI_SETUP, &d->dev_flags))
                        continue;
 
+               if (test_bit(HCI_USER_CHANNEL, &d->dev_flags))
+                       continue;
+
                if (!mgmt_valid_hdev(d))
                        continue;
 
@@ -381,8 +385,10 @@ static u32 get_supported_settings(struct hci_dev *hdev)
        if (enable_hs)
                settings |= MGMT_SETTING_HS;
 
-       if (lmp_le_capable(hdev))
+       if (lmp_le_capable(hdev)) {
                settings |= MGMT_SETTING_LE;
+               settings |= MGMT_SETTING_ADVERTISING;
+       }
 
        return settings;
 }
@@ -421,6 +427,9 @@ static u32 get_current_settings(struct hci_dev *hdev)
        if (test_bit(HCI_HS_ENABLED, &hdev->dev_flags))
                settings |= MGMT_SETTING_HS;
 
+       if (test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags))
+               settings |= MGMT_SETTING_ADVERTISING;
+
        return settings;
 }
 
@@ -804,6 +813,12 @@ static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data,
 
        hci_dev_lock(hdev);
 
+       if (mgmt_pending_find(MGMT_OP_SET_POWERED, hdev)) {
+               err = cmd_status(sk, hdev->id, MGMT_OP_SET_POWERED,
+                                MGMT_STATUS_BUSY);
+               goto failed;
+       }
+
        if (test_and_clear_bit(HCI_AUTO_OFF, &hdev->dev_flags)) {
                cancel_delayed_work(&hdev->power_off);
 
@@ -820,12 +835,6 @@ static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data,
                goto failed;
        }
 
-       if (mgmt_pending_find(MGMT_OP_SET_POWERED, hdev)) {
-               err = cmd_status(sk, hdev->id, MGMT_OP_SET_POWERED,
-                                MGMT_STATUS_BUSY);
-               goto failed;
-       }
-
        cmd = mgmt_pending_add(sk, MGMT_OP_SET_POWERED, hdev, data, len);
        if (!cmd) {
                err = -ENOMEM;
@@ -883,6 +892,36 @@ static int new_settings(struct hci_dev *hdev, struct sock *skip)
        return mgmt_event(MGMT_EV_NEW_SETTINGS, hdev, &ev, sizeof(ev), skip);
 }
 
+struct cmd_lookup {
+       struct sock *sk;
+       struct hci_dev *hdev;
+       u8 mgmt_status;
+};
+
+static void settings_rsp(struct pending_cmd *cmd, void *data)
+{
+       struct cmd_lookup *match = data;
+
+       send_settings_rsp(cmd->sk, cmd->opcode, match->hdev);
+
+       list_del(&cmd->list);
+
+       if (match->sk == NULL) {
+               match->sk = cmd->sk;
+               sock_hold(match->sk);
+       }
+
+       mgmt_pending_free(cmd);
+}
+
+static void cmd_status_rsp(struct pending_cmd *cmd, void *data)
+{
+       u8 *status = data;
+
+       cmd_status(cmd->sk, cmd->index, cmd->opcode, *status);
+       mgmt_pending_remove(cmd);
+}
+
 static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
                            u16 len)
 {
@@ -1321,11 +1360,32 @@ static int set_hs(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
        return send_settings_rsp(sk, MGMT_OP_SET_HS, hdev);
 }
 
+static void le_enable_complete(struct hci_dev *hdev, u8 status)
+{
+       struct cmd_lookup match = { NULL, hdev };
+
+       if (status) {
+               u8 mgmt_err = mgmt_status(status);
+
+               mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, cmd_status_rsp,
+                                    &mgmt_err);
+               return;
+       }
+
+       mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, settings_rsp, &match);
+
+       new_settings(hdev, match.sk);
+
+       if (match.sk)
+               sock_put(match.sk);
+}
+
 static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
 {
        struct mgmt_mode *cp = data;
        struct hci_cp_write_le_host_supported hci_cp;
        struct pending_cmd *cmd;
+       struct hci_request req;
        int err;
        u8 val, enabled;
 
@@ -1357,6 +1417,11 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
                        changed = true;
                }
 
+               if (!val && test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags)) {
+                       clear_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags);
+                       changed = true;
+               }
+
                err = send_settings_rsp(sk, MGMT_OP_SET_LE, hdev);
                if (err < 0)
                        goto unlock;
@@ -1367,7 +1432,8 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
                goto unlock;
        }
 
-       if (mgmt_pending_find(MGMT_OP_SET_LE, hdev)) {
+       if (mgmt_pending_find(MGMT_OP_SET_LE, hdev) ||
+           mgmt_pending_find(MGMT_OP_SET_ADVERTISING, hdev)) {
                err = cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
                                 MGMT_STATUS_BUSY);
                goto unlock;
@@ -1386,8 +1452,15 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
                hci_cp.simul = lmp_le_br_capable(hdev);
        }
 
-       err = hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(hci_cp),
-                          &hci_cp);
+       hci_req_init(&req, hdev);
+
+       if (test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags) && !val)
+               hci_req_add(&req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(val), &val);
+
+       hci_req_add(&req, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(hci_cp),
+                   &hci_cp);
+
+       err = hci_req_run(&req, le_enable_complete);
        if (err < 0)
                mgmt_pending_remove(cmd);
 
@@ -3065,6 +3138,98 @@ static int set_device_id(struct sock *sk, struct hci_dev *hdev, void *data,
        return err;
 }
 
+static void set_advertising_complete(struct hci_dev *hdev, u8 status)
+{
+       struct cmd_lookup match = { NULL, hdev };
+
+       if (status) {
+               u8 mgmt_err = mgmt_status(status);
+
+               mgmt_pending_foreach(MGMT_OP_SET_ADVERTISING, hdev,
+                                    cmd_status_rsp, &mgmt_err);
+               return;
+       }
+
+       mgmt_pending_foreach(MGMT_OP_SET_ADVERTISING, hdev, settings_rsp,
+                            &match);
+
+       new_settings(hdev, match.sk);
+
+       if (match.sk)
+               sock_put(match.sk);
+}
+
+static int set_advertising(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
+{
+       struct mgmt_mode *cp = data;
+       struct pending_cmd *cmd;
+       struct hci_request req;
+       u8 val, enabled;
+       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))
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
+                                 MGMT_STATUS_REJECTED);
+
+       if (cp->val != 0x00 && cp->val != 0x01)
+               return cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
+                                 MGMT_STATUS_INVALID_PARAMS);
+
+       hci_dev_lock(hdev);
+
+       val = !!cp->val;
+       enabled = test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags);
+
+       if (!hdev_is_powered(hdev) || val == enabled) {
+               bool changed = false;
+
+               if (val != test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags)) {
+                       change_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags);
+                       changed = true;
+               }
+
+               err = send_settings_rsp(sk, MGMT_OP_SET_ADVERTISING, hdev);
+               if (err < 0)
+                       goto unlock;
+
+               if (changed)
+                       err = new_settings(hdev, sk);
+
+               goto unlock;
+       }
+
+       if (mgmt_pending_find(MGMT_OP_SET_ADVERTISING, hdev) ||
+           mgmt_pending_find(MGMT_OP_SET_LE, hdev)) {
+               err = cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
+                                MGMT_STATUS_BUSY);
+               goto unlock;
+       }
+
+       cmd = mgmt_pending_add(sk, MGMT_OP_SET_ADVERTISING, hdev, data, len);
+       if (!cmd) {
+               err = -ENOMEM;
+               goto unlock;
+       }
+
+       hci_req_init(&req, hdev);
+
+       hci_req_add(&req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(val), &val);
+
+       err = hci_req_run(&req, set_advertising_complete);
+       if (err < 0)
+               mgmt_pending_remove(cmd);
+
+unlock:
+       hci_dev_unlock(hdev);
+       return err;
+}
+
 static void fast_connectable_complete(struct hci_dev *hdev, u8 status)
 {
        struct pending_cmd *cmd;
@@ -3276,6 +3441,7 @@ static const struct mgmt_handler {
        { block_device,           false, MGMT_BLOCK_DEVICE_SIZE },
        { unblock_device,         false, MGMT_UNBLOCK_DEVICE_SIZE },
        { set_device_id,          false, MGMT_SET_DEVICE_ID_SIZE },
+       { set_advertising,        false, MGMT_SETTING_SIZE },
 };
 
 
@@ -3320,6 +3486,12 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
                                         MGMT_STATUS_INVALID_INDEX);
                        goto done;
                }
+
+               if (test_bit(HCI_USER_CHANNEL, &hdev->dev_flags)) {
+                       err = cmd_status(sk, index, opcode,
+                                        MGMT_STATUS_INVALID_INDEX);
+                       goto done;
+               }
        }
 
        if (opcode >= ARRAY_SIZE(mgmt_handlers) ||
@@ -3365,14 +3537,6 @@ done:
        return err;
 }
 
-static void cmd_status_rsp(struct pending_cmd *cmd, void *data)
-{
-       u8 *status = data;
-
-       cmd_status(cmd->sk, cmd->index, cmd->opcode, *status);
-       mgmt_pending_remove(cmd);
-}
-
 int mgmt_index_added(struct hci_dev *hdev)
 {
        if (!mgmt_valid_hdev(hdev))
@@ -3393,28 +3557,6 @@ int mgmt_index_removed(struct hci_dev *hdev)
        return mgmt_event(MGMT_EV_INDEX_REMOVED, hdev, NULL, 0, NULL);
 }
 
-struct cmd_lookup {
-       struct sock *sk;
-       struct hci_dev *hdev;
-       u8 mgmt_status;
-};
-
-static void settings_rsp(struct pending_cmd *cmd, void *data)
-{
-       struct cmd_lookup *match = data;
-
-       send_settings_rsp(cmd->sk, cmd->opcode, match->hdev);
-
-       list_del(&cmd->list);
-
-       if (match->sk == NULL) {
-               match->sk = cmd->sk;
-               sock_hold(match->sk);
-       }
-
-       mgmt_pending_free(cmd);
-}
-
 static void set_bredr_scan(struct hci_request *req)
 {
        struct hci_dev *hdev = req->hdev;
@@ -3483,6 +3625,12 @@ static int powered_update_hci(struct hci_dev *hdev)
                                    sizeof(cp), &cp);
        }
 
+       if (test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags)) {
+               u8 adv = 0x01;
+
+               hci_req_add(&req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(adv), &adv);
+       }
+
        link_sec = test_bit(HCI_LINK_SECURITY, &hdev->dev_flags);
        if (link_sec != test_bit(HCI_AUTH, &hdev->flags))
                hci_req_add(&req, HCI_OP_WRITE_AUTH_ENABLE,
@@ -4132,44 +4280,6 @@ int mgmt_read_local_oob_data_reply_complete(struct hci_dev *hdev, u8 *hash,
        return err;
 }
 
-int mgmt_le_enable_complete(struct hci_dev *hdev, u8 enable, u8 status)
-{
-       struct cmd_lookup match = { NULL, hdev };
-       bool changed = false;
-       int err = 0;
-
-       if (status) {
-               u8 mgmt_err = mgmt_status(status);
-
-               if (enable && test_and_clear_bit(HCI_LE_ENABLED,
-                                                &hdev->dev_flags))
-                       err = new_settings(hdev, NULL);
-
-               mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, cmd_status_rsp,
-                                    &mgmt_err);
-
-               return err;
-       }
-
-       if (enable) {
-               if (!test_and_set_bit(HCI_LE_ENABLED, &hdev->dev_flags))
-                       changed = true;
-       } else {
-               if (test_and_clear_bit(HCI_LE_ENABLED, &hdev->dev_flags))
-                       changed = true;
-       }
-
-       mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, settings_rsp, &match);
-
-       if (changed)
-               err = new_settings(hdev, match.sk);
-
-       if (match.sk)
-               sock_put(match.sk);
-
-       return err;
-}
-
 int mgmt_device_found(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type,
                      u8 addr_type, u8 *dev_class, s8 rssi, u8 cfm_name, u8
                      ssp, u8 *eir, u16 eir_len)