#include <net/bluetooth/mgmt.h>
#ifdef TIZEN_BT
#include <net/bluetooth/mgmt_tizen.h>
+#include <net/bluetooth/sco.h>
#endif
#include "hci_request.h"
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_PRIVACY,
MGMT_STATUS_INVALID_PARAMS);
+#ifndef TIZEN_BT
+ /* commenting out since set privacy command is always rejected
+ * if this condition is enabled.
+ */
if (hdev_is_powered(hdev))
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_PRIVACY,
MGMT_STATUS_REJECTED);
+#endif
hci_dev_lock(hdev);
hci_dev_lock(hdev);
- if (pending_find(MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST, hdev)) {
+ if (pending_find(MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST, hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST,
+ MGMT_STATUS_BUSY);
+ goto unlocked;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST,
+ hdev, data, len);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlocked;
+ }
+
+ hci_req_init(&req, hdev);
+
+ hci_req_add(&req, HCI_OP_LE_DEL_FROM_WHITE_LIST, sizeof(*cp), cp);
+
+ err = hci_req_run(&req, remove_from_white_list_complete);
+ if (err < 0) {
+ mgmt_pending_remove(cmd);
+ goto unlocked;
+ }
+
+unlocked:
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+static void clear_white_list_complete(struct hci_dev *hdev, u8 status,
+ u16 opcode)
+{
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("status 0x%02x", status);
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_CLEAR_DEV_WHITE_LIST, hdev);
+ if (!cmd)
+ goto unlock;
+
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_CLEAR_DEV_WHITE_LIST,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id,
+ MGMT_OP_CLEAR_DEV_WHITE_LIST,
+ 0, NULL, 0);
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static int clear_white_list(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_pending_cmd *cmd;
+ struct hci_request req;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ if (!lmp_le_capable(hdev))
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_CLEAR_DEV_WHITE_LIST,
+ MGMT_STATUS_NOT_SUPPORTED);
+
+ if (!hdev_is_powered(hdev))
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_CLEAR_DEV_WHITE_LIST,
+ MGMT_STATUS_REJECTED);
+
+ hci_dev_lock(hdev);
+
+ if (pending_find(MGMT_OP_CLEAR_DEV_WHITE_LIST, hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_CLEAR_DEV_WHITE_LIST,
+ MGMT_STATUS_BUSY);
+ goto unlocked;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_CLEAR_DEV_WHITE_LIST,
+ hdev, NULL, 0);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlocked;
+ }
+
+ hci_req_init(&req, hdev);
+
+ hci_req_add(&req, HCI_OP_LE_CLEAR_WHITE_LIST, 0, NULL);
+
+ err = hci_req_run(&req, clear_white_list_complete);
+ if (err < 0) {
+ mgmt_pending_remove(cmd);
+ goto unlocked;
+ }
+
+unlocked:
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+static void set_scan_rsp_data_complete(struct hci_dev *hdev, u8 status,
+ u16 opcode)
+{
+ struct mgmt_cp_set_scan_rsp_data *cp;
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("status 0x%02x", status);
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_SET_SCAN_RSP_DATA, hdev);
+ if (!cmd)
+ goto unlock;
+
+ cp = cmd->param;
+
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_SCAN_RSP_DATA,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id,
+ MGMT_OP_SET_SCAN_RSP_DATA, 0,
+ cp, sizeof(*cp));
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static int set_scan_rsp_data(struct sock *sk, struct hci_dev *hdev, void *data,
+ u16 len)
+{
+ struct mgmt_pending_cmd *cmd;
+ struct hci_request req;
+ struct mgmt_cp_set_scan_rsp_data *cp = data;
+ struct hci_cp_le_set_scan_rsp_data rsp;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ if (!lmp_le_capable(hdev))
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_SCAN_RSP_DATA,
+ MGMT_STATUS_NOT_SUPPORTED);
+
+ hci_dev_lock(hdev);
+
+ if (pending_find(MGMT_OP_SET_SCAN_RSP_DATA, hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_SCAN_RSP_DATA,
+ MGMT_STATUS_BUSY);
+ goto unlocked;
+ }
+
+ if (len > HCI_MAX_AD_LENGTH) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_SCAN_RSP_DATA,
+ MGMT_STATUS_INVALID_PARAMS);
+ goto unlocked;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_SET_SCAN_RSP_DATA, hdev, data, len);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlocked;
+ }
+
+ hci_req_init(&req, hdev);
+
+ memset(&rsp, 0, sizeof(rsp));
+ memcpy(rsp.data, cp->data, len);
+ rsp.length = len;
+
+ hci_req_add(&req, HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(rsp), &rsp);
+
+ err = hci_req_run(&req, set_scan_rsp_data_complete);
+ if (err < 0)
+ mgmt_pending_remove(cmd);
+
+unlocked:
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+static void set_rssi_threshold_complete(struct hci_dev *hdev,
+ u8 status, u16 opcode)
+{
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("status 0x%02x", status);
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_SET_RSSI_ENABLE, hdev);
+ if (!cmd)
+ goto unlock;
+
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE, 0,
+ NULL, 0);
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static void set_rssi_disable_complete(struct hci_dev *hdev,
+ u8 status, u16 opcode)
+{
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("status 0x%02x", status);
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_SET_RSSI_DISABLE, hdev);
+ if (!cmd)
+ goto unlock;
+
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ 0, NULL, 0);
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+int mgmt_set_rssi_threshold(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ int err = 0;
+ struct hci_cp_set_rssi_threshold th = { 0, };
+ struct mgmt_cp_set_enable_rssi *cp = data;
+ struct hci_conn *conn;
+ struct mgmt_pending_cmd *cmd;
+ struct hci_request req;
+ __u8 dest_type;
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_SET_RSSI_ENABLE, hdev);
+ if (!cmd) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE,
+ MGMT_STATUS_FAILED);
+ goto unlocked;
+ }
+
+ if (!lmp_le_capable(hdev)) {
+ mgmt_pending_remove(cmd);
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlocked;
+ }
+
+ if (!hdev_is_powered(hdev)) {
+ BT_DBG("%s", hdev->name);
+ mgmt_pending_remove(cmd);
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE,
+ MGMT_STATUS_NOT_POWERED);
+ goto unlocked;
+ }
+
+ if (cp->link_type == 0x01)
+ dest_type = LE_LINK;
+ else
+ dest_type = ACL_LINK;
+
+ /* Get LE/ACL link handle info */
+ conn = hci_conn_hash_lookup_ba(hdev,
+ dest_type, &cp->bdaddr);
+
+ if (!conn) {
+ err = mgmt_cmd_complete(sk, hdev->id,
+ MGMT_OP_SET_RSSI_ENABLE, 1, NULL, 0);
+ mgmt_pending_remove(cmd);
+ goto unlocked;
+ }
+
+ hci_req_init(&req, hdev);
+
+ th.hci_le_ext_opcode = 0x0B;
+ th.mode = 0x01;
+ th.conn_handle = conn->handle;
+ th.alert_mask = 0x07;
+ th.low_th = cp->low_th;
+ th.in_range_th = cp->in_range_th;
+ th.high_th = cp->high_th;
+
+ hci_req_add(&req, HCI_OP_ENABLE_RSSI, sizeof(th), &th);
+ err = hci_req_run(&req, set_rssi_threshold_complete);
+
+ if (err < 0) {
+ mgmt_pending_remove(cmd);
+ BT_ERR("Error in requesting hci_req_run");
+ goto unlocked;
+ }
+
+unlocked:
+ hci_dev_unlock(hdev);
+ return err;
+}
+
+void mgmt_rssi_enable_success(struct sock *sk, struct hci_dev *hdev,
+ void *data, struct hci_cc_rsp_enable_rssi *rp, int success)
+{
+ struct mgmt_cc_rsp_enable_rssi mgmt_rp = { 0, };
+ struct mgmt_cp_set_enable_rssi *cp = data;
+ struct mgmt_pending_cmd *cmd;
+
+ if (!cp || !rp)
+ goto remove_cmd;
+
+ mgmt_rp.status = rp->status;
+ mgmt_rp.le_ext_opcode = rp->le_ext_opcode;
+ mgmt_rp.bt_address = cp->bdaddr;
+ mgmt_rp.link_type = cp->link_type;
+
+ mgmt_cmd_complete(sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE,
+ MGMT_STATUS_SUCCESS, &mgmt_rp,
+ sizeof(struct mgmt_cc_rsp_enable_rssi));
+
+ mgmt_event(MGMT_EV_RSSI_ENABLED, hdev, &mgmt_rp,
+ sizeof(struct mgmt_cc_rsp_enable_rssi), NULL);
+
+ hci_conn_rssi_unset_all(hdev, mgmt_rp.link_type);
+ hci_conn_rssi_state_set(hdev, mgmt_rp.link_type,
+ &mgmt_rp.bt_address, true);
+
+remove_cmd:
+ hci_dev_lock(hdev);
+ cmd = pending_find(MGMT_OP_SET_RSSI_ENABLE, hdev);
+ if (cmd)
+ mgmt_pending_remove(cmd);
+
+ hci_dev_unlock(hdev);
+}
+
+void mgmt_rssi_disable_success(struct sock *sk, struct hci_dev *hdev,
+ void *data, struct hci_cc_rsp_enable_rssi *rp, int success)
+{
+ struct mgmt_cc_rp_disable_rssi mgmt_rp = { 0, };
+ struct mgmt_cp_disable_rssi *cp = data;
+ struct mgmt_pending_cmd *cmd;
+
+ if (!cp || !rp)
+ goto remove_cmd;
+
+ mgmt_rp.status = rp->status;
+ mgmt_rp.le_ext_opcode = rp->le_ext_opcode;
+ mgmt_rp.bt_address = cp->bdaddr;
+ mgmt_rp.link_type = cp->link_type;
+
+ mgmt_cmd_complete(sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ MGMT_STATUS_SUCCESS, &mgmt_rp,
+ sizeof(struct mgmt_cc_rsp_enable_rssi));
+
+ mgmt_event(MGMT_EV_RSSI_DISABLED, hdev, &mgmt_rp,
+ sizeof(struct mgmt_cc_rsp_enable_rssi), NULL);
+
+ hci_conn_rssi_state_set(hdev, mgmt_rp.link_type,
+ &mgmt_rp.bt_address, false);
+
+remove_cmd:
+ hci_dev_lock(hdev);
+ cmd = pending_find(MGMT_OP_SET_RSSI_DISABLE, hdev);
+ if (cmd)
+ mgmt_pending_remove(cmd);
+
+ hci_dev_unlock(hdev);
+}
+
+static int mgmt_set_disable_rssi(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_pending_cmd *cmd;
+ struct hci_request req;
+ struct hci_cp_set_enable_rssi cp_en = { 0, };
+ int err;
+
+ BT_DBG("Set Disable RSSI.");
+
+ cp_en.hci_le_ext_opcode = 0x01;
+ cp_en.le_enable_cs_Features = 0x00;
+ cp_en.data[0] = 0x00;
+ cp_en.data[1] = 0x00;
+ cp_en.data[2] = 0x00;
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_SET_RSSI_DISABLE, hdev);
+ if (!cmd) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ MGMT_STATUS_FAILED);
+ goto unlocked;
+ }
+
+ if (!lmp_le_capable(hdev)) {
+ mgmt_pending_remove(cmd);
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlocked;
+ }
+
+ if (!hdev_is_powered(hdev)) {
+ BT_DBG("%s", hdev->name);
+ mgmt_pending_remove(cmd);
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ MGMT_STATUS_NOT_POWERED);
+ goto unlocked;
+ }
+
+ hci_req_init(&req, hdev);
+
+ BT_DBG("Enable Len: %zu [%2.2X %2.2X %2.2X %2.2X %2.2X]",
+ sizeof(struct hci_cp_set_enable_rssi),
+ cp_en.hci_le_ext_opcode, cp_en.le_enable_cs_Features,
+ cp_en.data[0], cp_en.data[1], cp_en.data[2]);
+
+ hci_req_add(&req, HCI_OP_ENABLE_RSSI, sizeof(cp_en), &cp_en);
+ err = hci_req_run(&req, set_rssi_disable_complete);
+
+ if (err < 0) {
+ mgmt_pending_remove(cmd);
+ BT_ERR("Error in requesting hci_req_run");
+ goto unlocked;
+ }
+
+unlocked:
+ hci_dev_unlock(hdev);
+ return err;
+}
+
+void mgmt_enable_rssi_cc(struct hci_dev *hdev, void *response, u8 status)
+{
+ struct hci_cc_rsp_enable_rssi *rp = response;
+ struct mgmt_pending_cmd *cmd_enable = NULL;
+ struct mgmt_pending_cmd *cmd_disable = NULL;
+ struct mgmt_cp_set_enable_rssi *cp_en;
+ struct mgmt_cp_disable_rssi *cp_dis;
+
+ hci_dev_lock(hdev);
+ cmd_enable = pending_find(MGMT_OP_SET_RSSI_ENABLE, hdev);
+ cmd_disable = pending_find(MGMT_OP_SET_RSSI_DISABLE, hdev);
+ hci_dev_unlock(hdev);
+
+ if (cmd_enable)
+ BT_DBG("Enable Request");
+
+ if (cmd_disable)
+ BT_DBG("Disable Request");
+
+ if (cmd_enable) {
+ cp_en = cmd_enable->param;
+
+ if (status != 0x00)
+ return;
+
+ switch (rp->le_ext_opcode) {
+ case 0x01:
+ BT_DBG("RSSI enabled.. Setting Threshold...");
+ mgmt_set_rssi_threshold(cmd_enable->sk, hdev,
+ cp_en, sizeof(*cp_en));
+ break;
+
+ case 0x0B:
+ BT_DBG("Sending RSSI enable success");
+ mgmt_rssi_enable_success(cmd_enable->sk, hdev,
+ cp_en, rp, rp->status);
+ break;
+ }
+
+ } else if (cmd_disable) {
+ cp_dis = cmd_disable->param;
+
+ if (status != 0x00)
+ return;
+
+ switch (rp->le_ext_opcode) {
+ case 0x01:
+ BT_DBG("Sending RSSI disable success");
+ mgmt_rssi_disable_success(cmd_disable->sk, hdev,
+ cp_dis, rp, rp->status);
+ break;
+
+ case 0x0B:
+ /*
+ * Only unset RSSI Threshold values for the Link if
+ * RSSI is monitored for other BREDR or LE Links
+ */
+ if (hci_conn_hash_lookup_rssi_count(hdev) > 1) {
+ BT_DBG("Unset Threshold. Other links being monitored");
+ mgmt_rssi_disable_success(cmd_disable->sk, hdev,
+ cp_dis, rp, rp->status);
+ } else {
+ BT_DBG("Unset Threshold. Disabling...");
+ mgmt_set_disable_rssi(cmd_disable->sk, hdev,
+ cp_dis, sizeof(*cp_dis));
+ }
+ break;
+ }
+ }
+}
+
+static void set_rssi_enable_complete(struct hci_dev *hdev, u8 status,
+ u16 opcode)
+{
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("status 0x%02x", status);
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_SET_RSSI_ENABLE, hdev);
+ if (!cmd)
+ goto unlock;
+
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE, 0,
+ NULL, 0);
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static int set_enable_rssi(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_pending_cmd *cmd;
+ struct hci_request req;
+ struct mgmt_cp_set_enable_rssi *cp = data;
+ struct hci_cp_set_enable_rssi cp_en = { 0, };
+ int err;
+
+ BT_DBG("Set Enable RSSI.");
+
+ cp_en.hci_le_ext_opcode = 0x01;
+ cp_en.le_enable_cs_Features = 0x04;
+ cp_en.data[0] = 0x00;
+ cp_en.data[1] = 0x00;
+ cp_en.data[2] = 0x00;
+
+ hci_dev_lock(hdev);
+
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlocked;
+ }
+
+ if (!hdev_is_powered(hdev)) {
+ BT_DBG("%s", hdev->name);
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE,
+ MGMT_STATUS_NOT_POWERED);
+ goto unlocked;
+ }
+
+ if (pending_find(MGMT_OP_SET_RSSI_ENABLE, hdev)) {
+ BT_DBG("%s", hdev->name);
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_ENABLE,
+ MGMT_STATUS_BUSY);
+ goto unlocked;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_SET_RSSI_ENABLE, hdev, cp,
+ sizeof(*cp));
+ if (!cmd) {
+ BT_DBG("%s", hdev->name);
+ err = -ENOMEM;
+ goto unlocked;
+ }
+
+ /* If RSSI is already enabled directly set Threshold values */
+ if (hci_conn_hash_lookup_rssi_count(hdev) > 0) {
+ hci_dev_unlock(hdev);
+ BT_DBG("RSSI Enabled. Directly set Threshold");
+ err = mgmt_set_rssi_threshold(sk, hdev, cp, sizeof(*cp));
+ return err;
+ }
+
+ hci_req_init(&req, hdev);
+
+ BT_DBG("Enable Len: %zu [%2.2X %2.2X %2.2X %2.2X %2.2X]",
+ sizeof(struct hci_cp_set_enable_rssi),
+ cp_en.hci_le_ext_opcode, cp_en.le_enable_cs_Features,
+ cp_en.data[0], cp_en.data[1], cp_en.data[2]);
+
+ hci_req_add(&req, HCI_OP_ENABLE_RSSI, sizeof(cp_en), &cp_en);
+ err = hci_req_run(&req, set_rssi_enable_complete);
+
+ if (err < 0) {
+ mgmt_pending_remove(cmd);
+ BT_ERR("Error in requesting hci_req_run");
+ goto unlocked;
+ }
+
+unlocked:
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+static void get_raw_rssi_complete(struct hci_dev *hdev, u8 status, u16 opcode)
+{
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("status 0x%02x", status);
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_GET_RAW_RSSI, hdev);
+ if (!cmd)
+ goto unlock;
+
+ mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_GET_RAW_RSSI,
+ MGMT_STATUS_SUCCESS, &status, 1);
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static int get_raw_rssi(struct sock *sk, struct hci_dev *hdev, void *data,
+ u16 len)
+{
+ struct mgmt_pending_cmd *cmd;
+ struct hci_request req;
+ struct mgmt_cp_get_raw_rssi *cp = data;
+ struct hci_cp_get_raw_rssi hci_cp;
+
+ struct hci_conn *conn;
+ int err;
+ __u8 dest_type;
+
+ BT_DBG("Get Raw RSSI.");
+
+ hci_dev_lock(hdev);
+
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_GET_RAW_RSSI,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlocked;
+ }
+
+ if (cp->link_type == 0x01)
+ dest_type = LE_LINK;
+ else
+ dest_type = ACL_LINK;
+
+ /* Get LE/BREDR link handle info */
+ conn = hci_conn_hash_lookup_ba(hdev,
+ dest_type, &cp->bt_address);
+ if (!conn) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_GET_RAW_RSSI,
+ MGMT_STATUS_NOT_CONNECTED);
+ goto unlocked;
+ }
+ hci_cp.conn_handle = conn->handle;
+
+ if (!hdev_is_powered(hdev)) {
+ BT_DBG("%s", hdev->name);
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_GET_RAW_RSSI,
+ MGMT_STATUS_NOT_POWERED);
+ goto unlocked;
+ }
+
+ if (pending_find(MGMT_OP_GET_RAW_RSSI, hdev)) {
+ BT_DBG("%s", hdev->name);
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_GET_RAW_RSSI,
+ MGMT_STATUS_BUSY);
+ goto unlocked;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_GET_RAW_RSSI, hdev, data, len);
+ if (!cmd) {
+ BT_DBG("%s", hdev->name);
+ err = -ENOMEM;
+ goto unlocked;
+ }
+
+ hci_req_init(&req, hdev);
+
+ BT_DBG("Connection Handle [%d]", hci_cp.conn_handle);
+ hci_req_add(&req, HCI_OP_GET_RAW_RSSI, sizeof(hci_cp), &hci_cp);
+ err = hci_req_run(&req, get_raw_rssi_complete);
+
+ if (err < 0) {
+ mgmt_pending_remove(cmd);
+ BT_ERR("Error in requesting hci_req_run");
+ }
+
+unlocked:
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+void mgmt_raw_rssi_response(struct hci_dev *hdev,
+ struct hci_cc_rp_get_raw_rssi *rp, int success)
+{
+ struct mgmt_cc_rp_get_raw_rssi mgmt_rp = { 0, };
+ struct hci_conn *conn;
+
+ mgmt_rp.status = rp->status;
+ mgmt_rp.rssi_dbm = rp->rssi_dbm;
+
+ conn = hci_conn_hash_lookup_handle(hdev, rp->conn_handle);
+ if (!conn)
+ return;
+
+ bacpy(&mgmt_rp.bt_address, &conn->dst);
+ if (conn->type == LE_LINK)
+ mgmt_rp.link_type = 0x01;
+ else
+ mgmt_rp.link_type = 0x00;
+
+ mgmt_event(MGMT_EV_RAW_RSSI, hdev, &mgmt_rp,
+ sizeof(struct mgmt_cc_rp_get_raw_rssi), NULL);
+}
+
+static void set_disable_threshold_complete(struct hci_dev *hdev,
+ u8 status, u16 opcode)
+{
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("status 0x%02x", status);
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_SET_RSSI_DISABLE, hdev);
+ if (!cmd)
+ goto unlock;
+
+ mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ MGMT_STATUS_SUCCESS, &status, 1);
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+/** Removes monitoring for a link*/
+static int set_disable_threshold(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ int err = 0;
+ struct hci_cp_set_rssi_threshold th = { 0, };
+ struct mgmt_cp_disable_rssi *cp = data;
+ struct hci_conn *conn;
+ struct mgmt_pending_cmd *cmd;
+ struct hci_request req;
+ __u8 dest_type;
+
+ BT_DBG("Set Disable RSSI.");
+
+ hci_dev_lock(hdev);
+
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlocked;
+ }
+
+ /* Get LE/ACL link handle info*/
+ if (cp->link_type == 0x01)
+ dest_type = LE_LINK;
+ else
+ dest_type = ACL_LINK;
+
+ conn = hci_conn_hash_lookup_ba(hdev, dest_type, &cp->bdaddr);
+ if (!conn) {
+ err = mgmt_cmd_complete(sk, hdev->id,
+ MGMT_OP_SET_RSSI_DISABLE, 1, NULL, 0);
+ goto unlocked;
+ }
+
+ th.hci_le_ext_opcode = 0x0B;
+ th.mode = 0x01;
+ th.conn_handle = conn->handle;
+ th.alert_mask = 0x00;
+ th.low_th = 0x00;
+ th.in_range_th = 0x00;
+ th.high_th = 0x00;
+
+ if (!hdev_is_powered(hdev)) {
+ BT_DBG("%s", hdev->name);
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ 0, data, len);
+ goto unlocked;
+ }
+
+ if (pending_find(MGMT_OP_SET_RSSI_DISABLE, hdev)) {
+ BT_DBG("%s", hdev->name);
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_RSSI_DISABLE,
+ MGMT_STATUS_BUSY);
+ goto unlocked;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_SET_RSSI_DISABLE, hdev, cp,
+ sizeof(*cp));
+ if (!cmd) {
+ BT_DBG("%s", hdev->name);
+ err = -ENOMEM;
+ goto unlocked;
+ }
+
+ hci_req_init(&req, hdev);
+
+ hci_req_add(&req, HCI_OP_ENABLE_RSSI, sizeof(th), &th);
+ err = hci_req_run(&req, set_disable_threshold_complete);
+ if (err < 0) {
+ mgmt_pending_remove(cmd);
+ BT_ERR("Error in requesting hci_req_run");
+ goto unlocked;
+ }
+
+unlocked:
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+void mgmt_rssi_alert_evt(struct hci_dev *hdev, u16 conn_handle,
+ s8 alert_type, s8 rssi_dbm)
+{
+ struct mgmt_ev_vendor_specific_rssi_alert mgmt_ev;
+ struct hci_conn *conn;
+
+ BT_DBG("RSSI alert [%2.2X %2.2X %2.2X]",
+ conn_handle, alert_type, rssi_dbm);
+
+ conn = hci_conn_hash_lookup_handle(hdev, conn_handle);
+
+ if (!conn) {
+ BT_ERR("RSSI alert Error: Device not found for handle");
+ return;
+ }
+ bacpy(&mgmt_ev.bdaddr, &conn->dst);
+
+ if (conn->type == LE_LINK)
+ mgmt_ev.link_type = 0x01;
+ else
+ mgmt_ev.link_type = 0x00;
+
+ mgmt_ev.alert_type = alert_type;
+ mgmt_ev.rssi_dbm = rssi_dbm;
+
+ mgmt_event(MGMT_EV_RSSI_ALERT, hdev, &mgmt_ev,
+ sizeof(struct mgmt_ev_vendor_specific_rssi_alert),
+ NULL);
+}
+
+static int mgmt_start_le_discovery_failed(struct hci_dev *hdev, u8 status)
+{
+ struct mgmt_pending_cmd *cmd;
+ u8 type;
+ int err;
+
+ hci_le_discovery_set_state(hdev, DISCOVERY_STOPPED);
+
+ cmd = pending_find(MGMT_OP_START_LE_DISCOVERY, hdev);
+ if (!cmd)
+ return -ENOENT;
+
+ type = hdev->le_discovery.type;
+
+ err = mgmt_cmd_complete(cmd->sk, hdev->id, cmd->opcode,
+ mgmt_status(status), &type, sizeof(type));
+ mgmt_pending_remove(cmd);
+
+ return err;
+}
+
+static void start_le_discovery_complete(struct hci_dev *hdev, u8 status,
+ u16 opcode)
+{
+ unsigned long timeout = 0;
+
+ BT_DBG("status %d", status);
+
+ if (status) {
+ hci_dev_lock(hdev);
+ mgmt_start_le_discovery_failed(hdev, status);
+ hci_dev_unlock(hdev);
+ return;
+ }
+
+ hci_dev_lock(hdev);
+ hci_le_discovery_set_state(hdev, DISCOVERY_FINDING);
+ hci_dev_unlock(hdev);
+
+ if (hdev->le_discovery.type != DISCOV_TYPE_LE)
+ BT_ERR("Invalid discovery type %d", hdev->le_discovery.type);
+
+ if (!timeout)
+ return;
+
+ queue_delayed_work(hdev->workqueue, &hdev->le_scan_disable, timeout);
+}
+
+static int start_le_discovery(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_cp_start_le_discovery *cp = data;
+ struct mgmt_pending_cmd *cmd;
+ struct hci_cp_le_set_scan_param param_cp;
+ struct hci_cp_le_set_scan_enable enable_cp;
+ struct hci_request req;
+ u8 status, own_addr_type;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ if (!hdev_is_powered(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_START_LE_DISCOVERY,
+ MGMT_STATUS_NOT_POWERED);
+ goto unlock;
+ }
+
+ if (hdev->le_discovery.state != DISCOVERY_STOPPED) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_START_LE_DISCOVERY,
+ MGMT_STATUS_BUSY);
+ goto unlock;
+ }
+
+ if (cp->type != DISCOV_TYPE_LE) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_START_LE_DISCOVERY,
+ MGMT_STATUS_INVALID_PARAMS);
+ goto unlock;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_START_LE_DISCOVERY, hdev, NULL, 0);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ hdev->le_discovery.type = cp->type;
+
+ hci_req_init(&req, hdev);
+
+ status = mgmt_le_support(hdev);
+ if (status) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_START_LE_DISCOVERY,
+ status);
+ mgmt_pending_remove(cmd);
+ goto unlock;
+ }
+
+ /* If controller is scanning, it means the background scanning
+ * is running. Thus, we should temporarily stop it in order to
+ * set the discovery scanning parameters.
+ */
+ if (hci_dev_test_flag(hdev, HCI_LE_SCAN))
+ hci_req_add_le_scan_disable(&req, false);
+
+ memset(¶m_cp, 0, sizeof(param_cp));
+
+ /* All active scans will be done with either a resolvable
+ * private address (when privacy feature has been enabled)
+ * or unresolvable private address.
+ */
+ err = hci_update_random_address_sync(hdev, true, hci_dev_test_flag(hdev, HCI_PRIVACY), &own_addr_type);
+ if (err < 0) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_START_LE_DISCOVERY,
+ MGMT_STATUS_FAILED);
+ mgmt_pending_remove(cmd);
+ goto unlock;
+ }
+
+ param_cp.type = hdev->le_scan_type;
+ param_cp.interval = cpu_to_le16(hdev->le_scan_interval);
+ param_cp.window = cpu_to_le16(hdev->le_scan_window);
+ param_cp.own_address_type = own_addr_type;
+ hci_req_add(&req, HCI_OP_LE_SET_SCAN_PARAM, sizeof(param_cp),
+ ¶m_cp);
+
+ memset(&enable_cp, 0, sizeof(enable_cp));
+ enable_cp.enable = LE_SCAN_ENABLE;
+ enable_cp.filter_dup = LE_SCAN_FILTER_DUP_DISABLE;
+
+ hci_req_add(&req, HCI_OP_LE_SET_SCAN_ENABLE, sizeof(enable_cp),
+ &enable_cp);
+
+ err = hci_req_run(&req, start_le_discovery_complete);
+ if (err < 0)
+ mgmt_pending_remove(cmd);
+ else
+ hci_le_discovery_set_state(hdev, DISCOVERY_STARTING);
+
+unlock:
+ return err;
+}
+
+static int mgmt_stop_le_discovery_failed(struct hci_dev *hdev, u8 status)
+{
+ struct mgmt_pending_cmd *cmd;
+ int err;
+
+ cmd = pending_find(MGMT_OP_STOP_LE_DISCOVERY, hdev);
+ if (!cmd)
+ return -ENOENT;
+
+ err = mgmt_cmd_complete(cmd->sk, hdev->id, cmd->opcode,
+ mgmt_status(status), &hdev->le_discovery.type,
+ sizeof(hdev->le_discovery.type));
+ mgmt_pending_remove(cmd);
+
+ return err;
+}
+
+static void stop_le_discovery_complete(struct hci_dev *hdev, u8 status,
+ u16 opcode)
+{
+ BT_DBG("status %d", status);
+
+ hci_dev_lock(hdev);
+
+ if (status) {
+ mgmt_stop_le_discovery_failed(hdev, status);
+ goto unlock;
+ }
+
+ hci_le_discovery_set_state(hdev, DISCOVERY_STOPPED);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static int stop_le_discovery(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_cp_stop_le_discovery *mgmt_cp = data;
+ struct mgmt_pending_cmd *cmd;
+ struct hci_request req;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ hci_dev_lock(hdev);
+
+ if (!hci_le_discovery_active(hdev)) {
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_STOP_LE_DISCOVERY,
+ MGMT_STATUS_REJECTED, &mgmt_cp->type,
+ sizeof(mgmt_cp->type));
+ goto unlock;
+ }
+
+ if (hdev->le_discovery.type != mgmt_cp->type) {
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_STOP_LE_DISCOVERY,
+ MGMT_STATUS_INVALID_PARAMS,
+ &mgmt_cp->type, sizeof(mgmt_cp->type));
+ goto unlock;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_STOP_LE_DISCOVERY, hdev, NULL, 0);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ hci_req_init(&req, hdev);
+
+ if (hdev->le_discovery.state != DISCOVERY_FINDING) {
+ BT_DBG("unknown le discovery state %u",
+ hdev->le_discovery.state);
+
+ mgmt_pending_remove(cmd);
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_STOP_LE_DISCOVERY,
+ MGMT_STATUS_FAILED, &mgmt_cp->type,
+ sizeof(mgmt_cp->type));
+ goto unlock;
+ }
+
+ cancel_delayed_work(&hdev->le_scan_disable);
+ hci_req_add_le_scan_disable(&req, false);
+
+ err = hci_req_run(&req, stop_le_discovery_complete);
+ if (err < 0)
+ mgmt_pending_remove(cmd);
+ else
+ hci_le_discovery_set_state(hdev, DISCOVERY_STOPPING);
+
+unlock:
+ hci_dev_unlock(hdev);
+ return err;
+}
+
+/* Separate LE discovery */
+void mgmt_le_discovering(struct hci_dev *hdev, u8 discovering)
+{
+ struct mgmt_ev_discovering ev;
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("%s le discovering %u", hdev->name, discovering);
+
+ if (discovering)
+ cmd = pending_find(MGMT_OP_START_LE_DISCOVERY, hdev);
+ else
+ cmd = pending_find(MGMT_OP_STOP_LE_DISCOVERY, hdev);
+
+ if (cmd) {
+ u8 type = hdev->le_discovery.type;
+
+ mgmt_cmd_complete(cmd->sk, hdev->id, cmd->opcode, 0, &type,
+ sizeof(type));
+ mgmt_pending_remove(cmd);
+ }
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = hdev->le_discovery.type;
+ ev.discovering = discovering;
+
+ mgmt_event(MGMT_EV_DISCOVERING, hdev, &ev, sizeof(ev), NULL);
+}
+
+static int disable_le_auto_connect(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ hci_dev_lock(hdev);
+
+ err = hci_send_cmd(hdev, HCI_OP_LE_CREATE_CONN_CANCEL, 0, NULL);
+ if (err < 0)
+ BT_ERR("HCI_OP_LE_CREATE_CONN_CANCEL is failed");
+
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+static inline int check_le_conn_update_param(u16 min, u16 max, u16 latency,
+ u16 to_multiplier)
+{
+ u16 max_latency;
+
+ if (min > max || min < 6 || max > 3200)
+ return -EINVAL;
+
+ if (to_multiplier < 10 || to_multiplier > 3200)
+ return -EINVAL;
+
+ if (max >= to_multiplier * 8)
+ return -EINVAL;
+
+ max_latency = (to_multiplier * 8 / max) - 1;
+
+ if (latency > 499 || latency > max_latency)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int le_conn_update(struct sock *sk, struct hci_dev *hdev, void *data,
+ u16 len)
+{
+ struct mgmt_cp_le_conn_update *cp = data;
+
+ struct hci_conn *conn;
+ u16 min, max, latency, supervision_timeout;
+ int err = -1;
+
+ if (!hdev_is_powered(hdev))
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_CONN_UPDATE,
+ MGMT_STATUS_NOT_POWERED);
+
+ min = __le16_to_cpu(cp->conn_interval_min);
+ max = __le16_to_cpu(cp->conn_interval_max);
+ latency = __le16_to_cpu(cp->conn_latency);
+ supervision_timeout = __le16_to_cpu(cp->supervision_timeout);
+
+ BT_DBG("min 0x%4.4x max 0x%4.4x latency: 0x%4.4x supervision_timeout: 0x%4.4x",
+ min, max, latency, supervision_timeout);
+
+ err = check_le_conn_update_param(min, max, latency,
+ supervision_timeout);
+
+ if (err < 0)
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_CONN_UPDATE,
+ MGMT_STATUS_INVALID_PARAMS);
+
+ hci_dev_lock(hdev);
+
+ conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr);
+ if (!conn) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_CONN_UPDATE,
+ MGMT_STATUS_NOT_CONNECTED);
+ hci_dev_unlock(hdev);
+ return err;
+ }
+
+ hci_dev_unlock(hdev);
+
+ hci_le_conn_update(conn, min, max, latency, supervision_timeout);
+
+ return mgmt_cmd_complete(sk, hdev->id, MGMT_OP_LE_CONN_UPDATE, 0,
+ NULL, 0);
+}
+
+static void set_manufacturer_data_complete(struct hci_dev *hdev, u8 status,
+ u16 opcode)
+{
+ struct mgmt_cp_set_manufacturer_data *cp;
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("status 0x%02x", status);
+
+ hci_dev_lock(hdev);
+
+ cmd = pending_find(MGMT_OP_SET_MANUFACTURER_DATA, hdev);
+ if (!cmd)
+ goto unlock;
+
+ cp = cmd->param;
+
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id,
+ MGMT_OP_SET_MANUFACTURER_DATA,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id,
+ MGMT_OP_SET_MANUFACTURER_DATA, 0,
+ cp, sizeof(*cp));
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static int set_manufacturer_data(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_pending_cmd *cmd;
+ struct hci_request req;
+ struct mgmt_cp_set_manufacturer_data *cp = data;
+ u8 old_data[HCI_MAX_EIR_LENGTH] = {0, };
+ u8 old_len;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ if (!lmp_bredr_capable(hdev))
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_MANUFACTURER_DATA,
+ MGMT_STATUS_NOT_SUPPORTED);
+
+ if (cp->data[0] == 0 ||
+ cp->data[0] - 1 > sizeof(hdev->manufacturer_data))
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_MANUFACTURER_DATA,
+ MGMT_STATUS_INVALID_PARAMS);
+
+ if (cp->data[1] != 0xFF)
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_MANUFACTURER_DATA,
+ MGMT_STATUS_NOT_SUPPORTED);
+
+ hci_dev_lock(hdev);
+
+ if (pending_find(MGMT_OP_SET_MANUFACTURER_DATA, hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_MANUFACTURER_DATA,
+ MGMT_STATUS_BUSY);
+ goto unlocked;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_SET_MANUFACTURER_DATA, hdev, data,
+ len);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlocked;
+ }
+
+ hci_req_init(&req, hdev);
+
+ /* if new data is same as previous data then return command
+ * complete event
+ */
+ if (hdev->manufacturer_len == cp->data[0] - 1 &&
+ !memcmp(hdev->manufacturer_data, cp->data + 2, cp->data[0] - 1)) {
+ mgmt_pending_remove(cmd);
+ mgmt_cmd_complete(sk, hdev->id, MGMT_OP_SET_MANUFACTURER_DATA,
+ 0, cp, sizeof(*cp));
+ err = 0;
+ goto unlocked;
+ }
+
+ old_len = hdev->manufacturer_len;
+ if (old_len > 0)
+ memcpy(old_data, hdev->manufacturer_data, old_len);
+
+ hdev->manufacturer_len = cp->data[0] - 1;
+ if (hdev->manufacturer_len > 0)
+ memcpy(hdev->manufacturer_data, cp->data + 2,
+ hdev->manufacturer_len);
+
+ hci_update_eir_sync(hdev);
+
+ err = hci_req_run(&req, set_manufacturer_data_complete);
+ if (err < 0) {
+ mgmt_pending_remove(cmd);
+ goto failed;
+ }
+
+unlocked:
+ hci_dev_unlock(hdev);
+
+ return err;
+
+failed:
+ memset(hdev->manufacturer_data, 0x00, sizeof(hdev->manufacturer_data));
+ hdev->manufacturer_len = old_len;
+ if (hdev->manufacturer_len > 0)
+ memcpy(hdev->manufacturer_data, old_data,
+ hdev->manufacturer_len);
+ hci_dev_unlock(hdev);
+ return err;
+}
+
+static int le_set_scan_params(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_cp_le_set_scan_params *cp = data;
+ __u16 interval, window;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ if (!lmp_le_capable(hdev))
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_SET_SCAN_PARAMS,
+ MGMT_STATUS_NOT_SUPPORTED);
+
+ interval = __le16_to_cpu(cp->interval);
+
+ if (interval < 0x0004 || interval > 0x4000)
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_SET_SCAN_PARAMS,
+ MGMT_STATUS_INVALID_PARAMS);
+
+ window = __le16_to_cpu(cp->window);
+
+ if (window < 0x0004 || window > 0x4000)
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_SET_SCAN_PARAMS,
+ MGMT_STATUS_INVALID_PARAMS);
+
+ if (window > interval)
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_SET_SCAN_PARAMS,
+ MGMT_STATUS_INVALID_PARAMS);
+
+ hci_dev_lock(hdev);
+
+ hdev->le_scan_type = cp->type;
+ hdev->le_scan_interval = interval;
+ hdev->le_scan_window = window;
+
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_LE_SET_SCAN_PARAMS, 0,
+ NULL, 0);
+
+ /* If background scan is running, restart it so new parameters are
+ * loaded.
+ */
+ if (hci_dev_test_flag(hdev, HCI_LE_SCAN) &&
+ hdev->discovery.state == DISCOVERY_STOPPED) {
+ struct hci_request req;
+
+ hci_req_init(&req, hdev);
+
+ hci_req_add_le_scan_disable(&req, false);
+ hci_req_add_le_passive_scan(&req);
+
+ hci_req_run(&req, NULL);
+ }
+
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+static int set_voice_setting(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_cp_set_voice_setting *cp = data;
+ struct hci_conn *conn;
+ struct hci_conn *sco_conn;
+
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ if (!lmp_bredr_capable(hdev)) {
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_VOICE_SETTING,
+ MGMT_STATUS_NOT_SUPPORTED);
+ }
+
+ hci_dev_lock(hdev);
+
+ conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr);
+ if (!conn) {
+ err = mgmt_cmd_complete(sk, hdev->id,
+ MGMT_OP_SET_VOICE_SETTING, 0, NULL, 0);
+ goto unlock;
+ }
+
+ conn->voice_setting = cp->voice_setting;
+ conn->sco_role = cp->sco_role;
+
+ sco_conn = hci_conn_hash_lookup_sco(hdev);
+ if (sco_conn && bacmp(&sco_conn->dst, &cp->bdaddr) != 0) {
+ BT_ERR("There is other SCO connection.");
+ goto done;
+ }
+
+ if (conn->sco_role == MGMT_SCO_ROLE_HANDSFREE) {
+ if (conn->voice_setting == 0x0063)
+ sco_connect_set_wbc(hdev);
+ else
+ sco_connect_set_nbc(hdev);
+ } else {
+ if (conn->voice_setting == 0x0063)
+ sco_connect_set_gw_wbc(hdev);
+ else
+ sco_connect_set_gw_nbc(hdev);
+ }
+
+done:
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_SET_VOICE_SETTING, 0,
+ cp, sizeof(cp));
+
+unlock:
+ hci_dev_unlock(hdev);
+ return err;
+}
+
+static int get_adv_tx_power(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_rp_get_adv_tx_power *rp;
+ size_t rp_len;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ hci_dev_lock(hdev);
+
+ rp_len = sizeof(*rp);
+ rp = kmalloc(rp_len, GFP_KERNEL);
+ if (!rp) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ rp->adv_tx_power = hdev->adv_tx_power;
+
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_GET_ADV_TX_POWER, 0, rp,
+ rp_len);
+
+ kfree(rp);
+
+unlock:
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+void mgmt_hardware_error(struct hci_dev *hdev, u8 err_code)
+{
+ struct mgmt_ev_hardware_error ev;
+
+ ev.error_code = err_code;
+ mgmt_event(MGMT_EV_HARDWARE_ERROR, hdev, &ev, sizeof(ev), NULL);
+}
+
+void mgmt_tx_timeout_error(struct hci_dev *hdev)
+{
+ mgmt_event(MGMT_EV_TX_TIMEOUT_ERROR, hdev, NULL, 0, NULL);
+}
+
+void mgmt_multi_adv_state_change_evt(struct hci_dev *hdev, u8 adv_instance,
+ u8 state_change_reason, u16 connection_handle)
+{
+ struct mgmt_ev_vendor_specific_multi_adv_state_changed mgmt_ev;
+
+ BT_DBG("Multi adv state changed [%2.2X %2.2X %2.2X]",
+ adv_instance, state_change_reason, connection_handle);
+
+ mgmt_ev.adv_instance = adv_instance;
+ mgmt_ev.state_change_reason = state_change_reason;
+ mgmt_ev.connection_handle = connection_handle;
+
+ mgmt_event(MGMT_EV_MULTI_ADV_STATE_CHANGED, hdev, &mgmt_ev,
+ sizeof(struct mgmt_ev_vendor_specific_multi_adv_state_changed),
+ NULL);
+}
+
+static int enable_bt_6lowpan(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ int err;
+ struct mgmt_cp_enable_6lowpan *cp = data;
+
+ BT_DBG("%s", hdev->name);
+
+ hci_dev_lock(hdev);
+
+ if (!hdev_is_powered(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ENABLE_6LOWPAN,
+ MGMT_STATUS_NOT_POWERED);
+ goto unlocked;
+ }
+
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ENABLE_6LOWPAN,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlocked;
+ }
+
+ if (cp->enable_6lowpan)
+ bt_6lowpan_enable();
+ else
+ bt_6lowpan_disable();
+
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_ENABLE_6LOWPAN,
+ MGMT_STATUS_SUCCESS, NULL, 0);
+unlocked:
+ hci_dev_unlock(hdev);
+ return err;
+}
+
+static int connect_bt_6lowpan(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_cp_connect_6lowpan *cp = data;
+ __u8 addr_type = ADDR_LE_DEV_PUBLIC;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ hci_dev_lock(hdev);
+
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_CONNECT_6LOWPAN,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlocked;
+ }
+
+ if (!hdev_is_powered(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_CONNECT_6LOWPAN,
+ MGMT_STATUS_REJECTED);
+ goto unlocked;
+ }
+
+ if (bdaddr_type_is_le(cp->addr.type)) {
+ if (cp->addr.type == BDADDR_LE_PUBLIC)
+ addr_type = ADDR_LE_DEV_PUBLIC;
+ else
+ addr_type = ADDR_LE_DEV_RANDOM;
+ } else {
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_CONNECT_6LOWPAN,
+ MGMT_STATUS_INVALID_PARAMS, NULL, 0);
+ goto unlocked;
+ }
+
+ hci_dev_unlock(hdev);
+
+ /* 6lowpan Connect */
+ err = _bt_6lowpan_connect(&cp->addr.bdaddr, cp->addr.type);
+
+ hci_dev_lock(hdev);
+
+ if (err < 0) {
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_CONNECT_6LOWPAN,
+ MGMT_STATUS_REJECTED, NULL, 0);
+
+ goto unlocked;
+ }
+
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_CONNECT_6LOWPAN, 0,
+ NULL, 0);
+unlocked:
+ hci_dev_unlock(hdev);
+ return err;
+}
+
+static int disconnect_bt_6lowpan(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_cp_disconnect_6lowpan *cp = data;
+ struct hci_conn *conn = NULL;
+ __u8 addr_type = ADDR_LE_DEV_PUBLIC;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ hci_dev_lock(hdev);
+
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT_6LOWPAN,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlocked;
+ }
+
+ if (!hdev_is_powered(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT_6LOWPAN,
+ MGMT_STATUS_REJECTED);
+ goto unlocked;
+ }
+
+ if (bdaddr_type_is_le(cp->addr.type)) {
+ if (cp->addr.type == BDADDR_LE_PUBLIC)
+ addr_type = ADDR_LE_DEV_PUBLIC;
+ else
+ addr_type = ADDR_LE_DEV_RANDOM;
+ } else {
+ err = mgmt_cmd_complete(sk, hdev->id,
+ MGMT_OP_DISCONNECT_6LOWPAN,
+ MGMT_STATUS_INVALID_PARAMS, NULL, 0);
+ goto unlocked;
+ }
+
+ conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr);
+ if (!conn) {
+ err = mgmt_cmd_complete(sk, hdev->id,
+ MGMT_OP_DISCONNECT_6LOWPAN,
+ MGMT_STATUS_NOT_CONNECTED, NULL, 0);
+ goto unlocked;
+ }
+
+ if (conn->dst_type != addr_type) {
+ err = mgmt_cmd_complete(sk, hdev->id,
+ MGMT_OP_DISCONNECT_6LOWPAN,
+ MGMT_STATUS_INVALID_PARAMS, NULL, 0);
+ goto unlocked;
+ }
+
+ if (conn->state != BT_CONNECTED) {
+ err = mgmt_cmd_complete(sk, hdev->id,
+ MGMT_OP_DISCONNECT_6LOWPAN,
+ MGMT_STATUS_NOT_CONNECTED, NULL, 0);
+ goto unlocked;
+ }
+
+ /* 6lowpan Disconnect */
+ err = _bt_6lowpan_disconnect(conn->l2cap_data, cp->addr.type);
+ if (err < 0) {
+ err = mgmt_cmd_complete(sk, hdev->id,
+ MGMT_OP_DISCONNECT_6LOWPAN,
+ MGMT_STATUS_REJECTED, NULL, 0);
+ goto unlocked;
+ }
+
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_CONNECT_6LOWPAN, 0,
+ NULL, 0);
+
+unlocked:
+ hci_dev_unlock(hdev);
+ return err;
+}
+
+void mgmt_6lowpan_conn_changed(struct hci_dev *hdev, char if_name[16],
+ bdaddr_t *bdaddr, u8 addr_type, bool connected)
+{
+ char buf[512];
+ struct mgmt_ev_6lowpan_conn_state_changed *ev = (void *)buf;
+ size_t ev_size;
+
+ memset(buf, 0, sizeof(buf));
+ bacpy(&ev->addr.bdaddr, bdaddr);
+ ev->addr.type = addr_type;
+ ev->connected = connected;
+ memcpy(ev->ifname, (__u8 *)if_name, 16);
+
+ ev_size = sizeof(*ev);
+
+ mgmt_event(MGMT_EV_6LOWPAN_CONN_STATE_CHANGED, hdev, ev, ev_size, NULL);
+}
+
+void mgmt_le_read_maximum_data_length_complete(struct hci_dev *hdev, u8 status)
+{
+ struct mgmt_pending_cmd *cmd;
+ struct mgmt_rp_le_read_maximum_data_length rp;
+
+ BT_DBG("%s status %u", hdev->name, status);
+
+ cmd = pending_find(MGMT_OP_LE_READ_MAXIMUM_DATA_LENGTH, hdev);
+ if (!cmd)
+ return;
+
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id,
+ MGMT_OP_LE_READ_MAXIMUM_DATA_LENGTH,
+ mgmt_status(status));
+
+ memset(&rp, 0, sizeof(rp));
+
+ rp.max_tx_octets = cpu_to_le16(hdev->le_max_tx_len);
+ rp.max_tx_time = cpu_to_le16(hdev->le_max_tx_time);
+ rp.max_rx_octets = cpu_to_le16(hdev->le_max_rx_len);
+ rp.max_rx_time = cpu_to_le16(hdev->le_max_rx_time);
+
+ mgmt_cmd_complete(cmd->sk, hdev->id,
+ MGMT_OP_LE_READ_MAXIMUM_DATA_LENGTH, 0,
+ &rp, sizeof(rp));
+
+ mgmt_pending_remove(cmd);
+}
+
+static int read_maximum_le_data_length(struct sock *sk,
+ struct hci_dev *hdev, void *data, u16 len)
+{
+ struct mgmt_pending_cmd *cmd;
+ int err;
+
+ BT_DBG("read_maximum_le_data_length %s", hdev->name);
+
+ hci_dev_lock(hdev);
+
+ if (!hdev_is_powered(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LE_READ_MAXIMUM_DATA_LENGTH,
+ MGMT_STATUS_NOT_POWERED);
+ goto unlock;
+ }
+
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LE_READ_MAXIMUM_DATA_LENGTH,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlock;
+ }
+
+ if (pending_find(MGMT_OP_LE_READ_MAXIMUM_DATA_LENGTH, hdev)) {
err = mgmt_cmd_status(sk, hdev->id,
- MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST,
- MGMT_STATUS_BUSY);
- goto unlocked;
+ MGMT_OP_LE_READ_MAXIMUM_DATA_LENGTH,
+ MGMT_STATUS_BUSY);
+ goto unlock;
}
- cmd = mgmt_pending_add(sk, MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST,
- hdev, data, len);
+ cmd = mgmt_pending_add(sk, MGMT_OP_LE_READ_MAXIMUM_DATA_LENGTH,
+ hdev, data, len);
if (!cmd) {
err = -ENOMEM;
- goto unlocked;
+ goto unlock;
}
- hci_req_init(&req, hdev);
-
- hci_req_add(&req, HCI_OP_LE_DEL_FROM_WHITE_LIST, sizeof(*cp), cp);
-
- err = hci_req_run(&req, remove_from_white_list_complete);
- if (err < 0) {
+ err = hci_send_cmd(hdev, HCI_OP_LE_READ_MAX_DATA_LEN, 0, NULL);
+ if (err < 0)
mgmt_pending_remove(cmd);
- goto unlocked;
- }
-unlocked:
+unlock:
hci_dev_unlock(hdev);
-
return err;
}
-static void clear_white_list_complete(struct hci_dev *hdev, u8 status,
- u16 opcode)
+void mgmt_le_write_host_suggested_data_length_complete(struct hci_dev *hdev,
+ u8 status)
{
struct mgmt_pending_cmd *cmd;
hci_dev_lock(hdev);
- cmd = pending_find(MGMT_OP_CLEAR_DEV_WHITE_LIST, hdev);
- if (!cmd)
+ cmd = pending_find(MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH, hdev);
+ if (!cmd) {
+ BT_ERR("cmd not found in the pending list");
goto unlock;
+ }
if (status)
- mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_CLEAR_DEV_WHITE_LIST,
- mgmt_status(status));
+ mgmt_cmd_status(cmd->sk, hdev->id,
+ MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
+ mgmt_status(status));
else
mgmt_cmd_complete(cmd->sk, hdev->id,
- MGMT_OP_CLEAR_DEV_WHITE_LIST,
- 0, NULL, 0);
+ MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
+ 0, NULL, 0);
mgmt_pending_remove(cmd);
hci_dev_unlock(hdev);
}
-static int clear_white_list(struct sock *sk, struct hci_dev *hdev,
- void *data, u16 len)
+static int write_host_suggested_le_data_length(struct sock *sk,
+ struct hci_dev *hdev, void *data, u16 len)
{
struct mgmt_pending_cmd *cmd;
- struct hci_request req;
- int err;
+ struct mgmt_cp_le_write_host_suggested_data_length *cp = data;
+ struct hci_cp_le_write_def_data_len hci_data;
+ int err = 0;
- BT_DBG("%s", hdev->name);
+ BT_DBG("Write host suggested data length request for %s", hdev->name);
- if (!lmp_le_capable(hdev))
- return mgmt_cmd_status(sk, hdev->id,
- MGMT_OP_CLEAR_DEV_WHITE_LIST,
- MGMT_STATUS_NOT_SUPPORTED);
+ hci_dev_lock(hdev);
- if (!hdev_is_powered(hdev))
- return mgmt_cmd_status(sk, hdev->id,
- MGMT_OP_CLEAR_DEV_WHITE_LIST,
- MGMT_STATUS_REJECTED);
+ if (!hdev_is_powered(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
+ MGMT_STATUS_NOT_POWERED);
+ goto unlock;
+ }
- hci_dev_lock(hdev);
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlock;
+ }
- if (pending_find(MGMT_OP_CLEAR_DEV_WHITE_LIST, hdev)) {
+ if (pending_find(MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH, hdev)) {
err = mgmt_cmd_status(sk, hdev->id,
- MGMT_OP_CLEAR_DEV_WHITE_LIST,
+ MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
MGMT_STATUS_BUSY);
- goto unlocked;
+ goto unlock;
}
- cmd = mgmt_pending_add(sk, MGMT_OP_CLEAR_DEV_WHITE_LIST,
- hdev, NULL, 0);
+ cmd = mgmt_pending_add(sk, MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
+ hdev, data, len);
if (!cmd) {
err = -ENOMEM;
- goto unlocked;
+ goto unlock;
}
- hci_req_init(&req, hdev);
-
- hci_req_add(&req, HCI_OP_LE_CLEAR_WHITE_LIST, 0, NULL);
+ hci_data.tx_len = cp->def_tx_octets;
+ hci_data.tx_time = cp->def_tx_time;
- err = hci_req_run(&req, clear_white_list_complete);
- if (err < 0) {
+ err = hci_send_cmd(hdev, HCI_OP_LE_WRITE_DEF_DATA_LEN,
+ sizeof(hci_data), &hci_data);
+ if (err < 0)
mgmt_pending_remove(cmd);
- goto unlocked;
- }
-unlocked:
+unlock:
hci_dev_unlock(hdev);
return err;
}
-static void set_scan_rsp_data_complete(struct hci_dev *hdev, u8 status,
- u16 opcode)
+void mgmt_le_read_host_suggested_data_length_complete(struct hci_dev *hdev,
+ u8 status)
{
- struct mgmt_cp_set_scan_rsp_data *cp;
struct mgmt_pending_cmd *cmd;
+ struct mgmt_rp_le_read_host_suggested_data_length rp;
- BT_DBG("status 0x%02x", status);
+ BT_DBG("%s status %u", hdev->name, status);
+
+ cmd = pending_find(MGMT_OP_LE_READ_HOST_SUGGESTED_DATA_LENGTH, hdev);
+ if (!cmd) {
+ BT_ERR("cmd not found in the pending list");
+ return;
+ }
+
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id,
+ MGMT_OP_LE_READ_HOST_SUGGESTED_DATA_LENGTH,
+ mgmt_status(status));
+
+ memset(&rp, 0, sizeof(rp));
+
+ rp.def_tx_octets = cpu_to_le16(hdev->le_def_tx_len);
+ rp.def_tx_time = cpu_to_le16(hdev->le_def_tx_time);
+
+ mgmt_cmd_complete(cmd->sk, hdev->id,
+ MGMT_OP_LE_READ_HOST_SUGGESTED_DATA_LENGTH, 0,
+ &rp, sizeof(rp));
+
+ mgmt_pending_remove(cmd);
+}
+
+static int read_host_suggested_data_length(struct sock *sk,
+ struct hci_dev *hdev, void *data, u16 len)
+{
+ struct mgmt_pending_cmd *cmd;
+ int err;
+
+ BT_DBG("read_host_suggested_data_length %s", hdev->name);
hci_dev_lock(hdev);
- cmd = pending_find(MGMT_OP_SET_SCAN_RSP_DATA, hdev);
- if (!cmd)
+ if (!hdev_is_powered(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LE_READ_HOST_SUGGESTED_DATA_LENGTH,
+ MGMT_STATUS_NOT_POWERED);
goto unlock;
+ }
- cp = cmd->param;
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LE_READ_HOST_SUGGESTED_DATA_LENGTH,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlock;
+ }
- if (status)
- mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_SCAN_RSP_DATA,
- mgmt_status(status));
- else
- mgmt_cmd_complete(cmd->sk, hdev->id,
- MGMT_OP_SET_SCAN_RSP_DATA, 0,
- cp, sizeof(*cp));
+ if (pending_find(MGMT_OP_LE_READ_HOST_SUGGESTED_DATA_LENGTH, hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LE_READ_HOST_SUGGESTED_DATA_LENGTH,
+ MGMT_STATUS_BUSY);
+ goto unlock;
+ }
- mgmt_pending_remove(cmd);
+ cmd = mgmt_pending_add(sk, MGMT_OP_LE_READ_HOST_SUGGESTED_DATA_LENGTH,
+ hdev, data, len);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ err = hci_send_cmd(hdev, HCI_OP_LE_READ_DEF_DATA_LEN, 0, NULL);
+ if (err < 0)
+ mgmt_pending_remove(cmd);
unlock:
hci_dev_unlock(hdev);
+
+ return err;
}
-static int set_scan_rsp_data(struct sock *sk, struct hci_dev *hdev, void *data,
- u16 len)
+static int set_le_data_length_params(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
{
+ struct mgmt_cp_le_set_data_length *cp = data;
+ struct mgmt_rp_le_set_data_length *rp;
struct mgmt_pending_cmd *cmd;
- struct hci_request req;
- struct mgmt_cp_set_scan_rsp_data *cp = data;
- struct hci_cp_le_set_scan_rsp_data rsp;
- int err;
-
- BT_DBG("%s", hdev->name);
+ struct hci_conn *conn;
+ int err = 0;
+ u16 max_tx_octets, max_tx_time;
+ size_t rp_len;
- if (!lmp_le_capable(hdev))
- return mgmt_cmd_status(sk, hdev->id,
- MGMT_OP_SET_SCAN_RSP_DATA,
- MGMT_STATUS_NOT_SUPPORTED);
+ BT_INFO("Set Data length for the device %s", hdev->name);
hci_dev_lock(hdev);
- if (pending_find(MGMT_OP_SET_SCAN_RSP_DATA, hdev)) {
- err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_SCAN_RSP_DATA,
- MGMT_STATUS_BUSY);
- goto unlocked;
+ rp_len = sizeof(*rp);
+ rp = kmalloc(rp_len, GFP_KERNEL);
+ if (!rp) {
+ err = -ENOMEM;
+ goto unlock;
}
- if (len > HCI_MAX_AD_LENGTH) {
- err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_SCAN_RSP_DATA,
- MGMT_STATUS_INVALID_PARAMS);
- goto unlocked;
+ if (!hdev_is_powered(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_SET_DATA_LENGTH,
+ MGMT_STATUS_NOT_POWERED);
+ goto unlock;
}
- cmd = mgmt_pending_add(sk, MGMT_OP_SET_SCAN_RSP_DATA, hdev, data, len);
+ if (!lmp_le_capable(hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_SET_DATA_LENGTH,
+ MGMT_STATUS_NOT_SUPPORTED);
+ goto unlock;
+ }
+
+ if (pending_find(MGMT_OP_LE_SET_DATA_LENGTH, hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_SET_DATA_LENGTH,
+ MGMT_STATUS_BUSY);
+ goto unlock;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_LE_SET_DATA_LENGTH, hdev, data, len);
if (!cmd) {
err = -ENOMEM;
- goto unlocked;
+ goto unlock;
}
- hci_req_init(&req, hdev);
+ max_tx_octets = __le16_to_cpu(cp->max_tx_octets);
+ max_tx_time = __le16_to_cpu(cp->max_tx_time);
- memset(&rsp, 0, sizeof(rsp));
- memcpy(rsp.data, cp->data, len);
- rsp.length = len;
+ BT_DBG("max_tx_octets 0x%4.4x max_tx_time 0x%4.4x latency",
+ max_tx_octets, max_tx_time);
- hci_req_add(&req, HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(rsp), &rsp);
+ conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr);
+ if (!conn) {
+ mgmt_cmd_status(sk, hdev->id, MGMT_OP_LE_SET_DATA_LENGTH,
+ MGMT_STATUS_NOT_CONNECTED);
+ goto unlock;
+ }
- err = hci_req_run(&req, set_scan_rsp_data_complete);
+ hci_dev_unlock(hdev);
+
+ err = hci_le_set_data_length(conn, max_tx_octets, max_tx_time);
if (err < 0)
mgmt_pending_remove(cmd);
-unlocked:
+ rp->handle = conn->handle;
+ rp->status = 0;
+
+ hci_dev_lock(hdev);
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_LE_SET_DATA_LENGTH, 0,
+ rp, rp_len);
+unlock:
+ kfree(rp);
hci_dev_unlock(hdev);
return err;
}
+
+void mgmt_le_data_length_change_complete(struct hci_dev *hdev,
+ bdaddr_t *bdaddr, u16 tx_octets, u16 tx_time,
+ u16 rx_octets, u16 rx_time)
+{
+ struct mgmt_ev_le_data_length_changed ev;
+
+ bacpy(&ev.addr.bdaddr, bdaddr);
+ ev.max_tx_octets = tx_octets;
+ ev.max_tx_time = tx_time;
+ ev.max_rx_octets = rx_octets;
+ ev.max_rx_time = rx_time;
+
+ mgmt_event(MGMT_EV_LE_DATA_LENGTH_CHANGED, hdev, &ev, sizeof(ev), NULL);
+}
#endif /* TIZEN_BT */
static bool ltk_is_valid(struct mgmt_ltk_info *key)
return err;
}
+#ifdef TIZEN_BT
+int mgmt_device_name_update(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 *name,
+ u8 name_len)
+{
+ char buf[512];
+ struct mgmt_ev_device_name_update *ev = (void *)buf;
+ u16 eir_len = 0;
+
+ if (name_len <= 0)
+ return -EINVAL;
+
+ bacpy(&ev->addr.bdaddr, bdaddr);
+ ev->addr.type = BDADDR_BREDR;
+
+ eir_len = eir_append_data(ev->eir, 0, EIR_NAME_COMPLETE, name,
+ name_len);
+
+ ev->eir_len = cpu_to_le16(eir_len);
+
+ return mgmt_event(MGMT_EV_DEVICE_NAME_UPDATE, hdev, buf,
+ sizeof(*ev) + eir_len, NULL);
+}
+
+int mgmt_le_conn_update_failed(struct hci_dev *hdev, bdaddr_t *bdaddr,
+ u8 link_type, u8 addr_type, u8 status)
+{
+ struct mgmt_ev_conn_update_failed ev;
+
+ bacpy(&ev.addr.bdaddr, bdaddr);
+ ev.addr.type = link_to_bdaddr(link_type, addr_type);
+ ev.status = status;
+
+ return mgmt_event(MGMT_EV_CONN_UPDATE_FAILED, hdev,
+ &ev, sizeof(ev), NULL);
+}
+
+int mgmt_le_conn_updated(struct hci_dev *hdev, bdaddr_t *bdaddr,
+ u8 link_type, u8 addr_type, u16 conn_interval,
+ u16 conn_latency, u16 supervision_timeout)
+{
+ struct mgmt_ev_conn_updated ev;
+
+ bacpy(&ev.addr.bdaddr, bdaddr);
+ ev.addr.type = link_to_bdaddr(link_type, addr_type);
+ ev.conn_interval = cpu_to_le16(conn_interval);
+ ev.conn_latency = cpu_to_le16(conn_latency);
+ ev.supervision_timeout = cpu_to_le16(supervision_timeout);
+
+ return mgmt_event(MGMT_EV_CONN_UPDATED, hdev,
+ &ev, sizeof(ev), NULL);
+}
+
+/* le device found event - Pass adv type */
+void mgmt_le_device_found(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type,
+ u8 addr_type, u8 *dev_class, s8 rssi, u32 flags, u8 *eir,
+ u16 eir_len, u8 *scan_rsp, u8 scan_rsp_len, u8 adv_type)
+{
+ char buf[512];
+ struct mgmt_ev_le_device_found *ev = (void *)buf;
+ size_t ev_size;
+
+ if (!hci_discovery_active(hdev) && !hci_le_discovery_active(hdev))
+ return;
+
+ /* Make sure that the buffer is big enough. The 5 extra bytes
+ * are for the potential CoD field.
+ */
+ if (sizeof(*ev) + eir_len + scan_rsp_len + 5 > sizeof(buf))
+ return;
+
+ memset(buf, 0, sizeof(buf));
+
+ bacpy(&ev->addr.bdaddr, bdaddr);
+ ev->addr.type = link_to_bdaddr(link_type, addr_type);
+ ev->rssi = rssi;
+ ev->flags = cpu_to_le32(flags);
+ ev->adv_type = adv_type;
+
+ if (eir_len > 0)
+ memcpy(ev->eir, eir, eir_len);
+
+ if (dev_class && !eir_get_data(ev->eir, eir_len, EIR_CLASS_OF_DEV, NULL))
+ eir_len = eir_append_data(ev->eir, eir_len, EIR_CLASS_OF_DEV,
+ dev_class, 3);
+
+ if (scan_rsp_len > 0)
+ memcpy(ev->eir + eir_len, scan_rsp, scan_rsp_len);
+
+ ev->eir_len = cpu_to_le16(eir_len + scan_rsp_len);
+ ev_size = sizeof(*ev) + eir_len + scan_rsp_len;
+
+ mgmt_event(MGMT_EV_LE_DEVICE_FOUND, hdev, ev, ev_size, NULL);
+}
+#endif
+
static void read_local_oob_ext_data_complete(struct hci_dev *hdev, void *data,
int err)
{
{ add_white_list, MGMT_ADD_DEV_WHITE_LIST_SIZE },
{ remove_from_white_list, MGMT_REMOVE_DEV_FROM_WHITE_LIST_SIZE },
{ clear_white_list, MGMT_OP_CLEAR_DEV_WHITE_LIST_SIZE },
+ { set_enable_rssi, MGMT_SET_RSSI_ENABLE_SIZE },
+ { get_raw_rssi, MGMT_GET_RAW_RSSI_SIZE },
+ { set_disable_threshold, MGMT_SET_RSSI_DISABLE_SIZE },
+ { start_le_discovery, MGMT_START_LE_DISCOVERY_SIZE },
+ { stop_le_discovery, MGMT_STOP_LE_DISCOVERY_SIZE },
+ { disable_le_auto_connect, MGMT_DISABLE_LE_AUTO_CONNECT_SIZE },
+ { le_conn_update, MGMT_LE_CONN_UPDATE_SIZE },
+ { set_manufacturer_data, MGMT_SET_MANUFACTURER_DATA_SIZE },
+ { le_set_scan_params, MGMT_LE_SET_SCAN_PARAMS_SIZE },
+ { set_voice_setting, MGMT_SET_VOICE_SETTING_SIZE },
+ { get_adv_tx_power, MGMT_GET_ADV_TX_POWER_SIZE },
+ { enable_bt_6lowpan, MGMT_ENABLE_BT_6LOWPAN_SIZE },
+ { connect_bt_6lowpan, MGMT_CONNECT_6LOWPAN_SIZE },
+ { disconnect_bt_6lowpan, MGMT_DISCONNECT_6LOWPAN_SIZE },
+ { read_maximum_le_data_length,
+ MGMT_LE_READ_MAXIMUM_DATA_LENGTH_SIZE },
+ { write_host_suggested_le_data_length,
+ MGMT_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH_SIZE },
+ { read_host_suggested_data_length,
+ MGMT_LE_READ_HOST_SUGGESTED_DATA_LENGTH_SIZE },
+ { set_le_data_length_params,
+ MGMT_LE_SET_DATA_LENGTH_SIZE },
};
#endif