#include <net/bluetooth/hci_sock.h>
#include <net/bluetooth/l2cap.h>
#include <net/bluetooth/mgmt.h>
+#ifdef TIZEN_BT
+#include <net/bluetooth/mgmt_tizen.h>
+#include <net/bluetooth/sco.h>
+#endif
#include "hci_request.h"
#include "smp.h"
hdev->set_bdaddr)
settings |= MGMT_SETTING_CONFIGURATION;
+ if (cis_central_capable(hdev))
+ settings |= MGMT_SETTING_CIS_CENTRAL;
+
+ if (cis_peripheral_capable(hdev))
+ settings |= MGMT_SETTING_CIS_PERIPHERAL;
+
settings |= MGMT_SETTING_PHY_CONFIGURATION;
return settings;
if (hci_dev_test_flag(hdev, HCI_WIDEBAND_SPEECH_ENABLED))
settings |= MGMT_SETTING_WIDEBAND_SPEECH;
+ if (cis_central_capable(hdev))
+ settings |= MGMT_SETTING_CIS_CENTRAL;
+
+ if (cis_peripheral_capable(hdev))
+ settings |= MGMT_SETTING_CIS_PERIPHERAL;
+
return settings;
}
MGMT_OP_SET_EXP_FEATURE,
MGMT_STATUS_INVALID_INDEX);
- /* Changes can only be made when controller is powered down */
- if (hdev_is_powered(hdev))
- return mgmt_cmd_status(sk, hdev->id,
- MGMT_OP_SET_EXP_FEATURE,
- MGMT_STATUS_REJECTED);
-
/* Parameters are limited to a single octet */
if (data_len != MGMT_SET_EXP_FEATURE_SIZE + 1)
return mgmt_cmd_status(sk, hdev->id,
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);
return err;
}
-static bool ltk_is_valid(struct mgmt_ltk_info *key)
+#ifdef TIZEN_BT
+static int set_advertising_params(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
{
- if (key->initiator != 0x00 && key->initiator != 0x01)
- return false;
+ struct mgmt_cp_set_advertising_params *cp = data;
+ __u16 min_interval;
+ __u16 max_interval;
+ int err;
- switch (key->addr.type) {
- case BDADDR_LE_PUBLIC:
- return true;
+ BT_DBG("%s", hdev->name);
- case BDADDR_LE_RANDOM:
- /* Two most significant bits shall be set */
- if ((key->addr.bdaddr.b[5] & 0xc0) != 0xc0)
- return false;
- return true;
- }
+ if (!lmp_le_capable(hdev))
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_ADVERTISING_PARAMS,
+ MGMT_STATUS_NOT_SUPPORTED);
- return false;
+ if (hci_dev_test_flag(hdev, HCI_ADVERTISING))
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_ADVERTISING_PARAMS,
+ MGMT_STATUS_BUSY);
+
+ min_interval = __le16_to_cpu(cp->interval_min);
+ max_interval = __le16_to_cpu(cp->interval_max);
+
+ if (min_interval > max_interval ||
+ min_interval < 0x0020 || max_interval > 0x4000)
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_ADVERTISING_PARAMS,
+ MGMT_STATUS_INVALID_PARAMS);
+
+ hci_dev_lock(hdev);
+
+ hdev->le_adv_min_interval = min_interval;
+ hdev->le_adv_max_interval = max_interval;
+ hdev->adv_filter_policy = cp->filter_policy;
+ hdev->adv_type = cp->type;
+
+ err = mgmt_cmd_complete(sk, hdev->id,
+ MGMT_OP_SET_ADVERTISING_PARAMS, 0, NULL, 0);
+
+ hci_dev_unlock(hdev);
+
+ return err;
}
-static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
- void *cp_data, u16 len)
+static void set_advertising_data_complete(struct hci_dev *hdev,
+ u8 status, u16 opcode)
{
- struct mgmt_cp_load_long_term_keys *cp = cp_data;
- const u16 max_key_count = ((U16_MAX - sizeof(*cp)) /
- sizeof(struct mgmt_ltk_info));
- u16 key_count, expected_len;
- int i, err;
+ struct mgmt_cp_set_advertising_data *cp;
+ struct mgmt_pending_cmd *cmd;
- bt_dev_dbg(hdev, "sock %p", sk);
+ BT_DBG("status 0x%02x", status);
- if (!lmp_le_capable(hdev))
- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS,
- MGMT_STATUS_NOT_SUPPORTED);
+ hci_dev_lock(hdev);
- key_count = __le16_to_cpu(cp->key_count);
- if (key_count > max_key_count) {
- bt_dev_err(hdev, "load_ltks: too big key_count value %u",
- key_count);
- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS,
- MGMT_STATUS_INVALID_PARAMS);
- }
+ cmd = pending_find(MGMT_OP_SET_ADVERTISING_DATA, hdev);
+ if (!cmd)
+ goto unlock;
- expected_len = struct_size(cp, keys, key_count);
- if (expected_len != len) {
- bt_dev_err(hdev, "load_keys: expected %u bytes, got %u bytes",
- expected_len, len);
- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS,
- MGMT_STATUS_INVALID_PARAMS);
- }
+ cp = cmd->param;
- bt_dev_dbg(hdev, "key_count %u", key_count);
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id,
+ MGMT_OP_SET_ADVERTISING_DATA,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id,
+ MGMT_OP_SET_ADVERTISING_DATA, 0,
+ cp, sizeof(*cp));
- for (i = 0; i < key_count; i++) {
- struct mgmt_ltk_info *key = &cp->keys[i];
+ mgmt_pending_remove(cmd);
- if (!ltk_is_valid(key))
- return mgmt_cmd_status(sk, hdev->id,
- MGMT_OP_LOAD_LONG_TERM_KEYS,
- MGMT_STATUS_INVALID_PARAMS);
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static int set_advertising_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_advertising_data *cp = data;
+ struct hci_cp_le_set_adv_data adv;
+ int err;
+
+ BT_DBG("%s", hdev->name);
+
+ if (!lmp_le_capable(hdev)) {
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_ADVERTISING_DATA,
+ MGMT_STATUS_NOT_SUPPORTED);
}
hci_dev_lock(hdev);
- hci_smp_ltks_clear(hdev);
+ if (pending_find(MGMT_OP_SET_ADVERTISING_DATA, hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_ADVERTISING_DATA,
+ MGMT_STATUS_BUSY);
+ goto unlocked;
+ }
- for (i = 0; i < key_count; i++) {
- struct mgmt_ltk_info *key = &cp->keys[i];
- u8 type, authenticated;
+ if (len > HCI_MAX_AD_LENGTH) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_SET_ADVERTISING_DATA,
+ MGMT_STATUS_INVALID_PARAMS);
+ goto unlocked;
+ }
- if (hci_is_blocked_key(hdev,
- HCI_BLOCKED_KEY_TYPE_LTK,
- key->val)) {
- bt_dev_warn(hdev, "Skipping blocked LTK for %pMR",
- &key->addr.bdaddr);
- continue;
- }
+ cmd = mgmt_pending_add(sk, MGMT_OP_SET_ADVERTISING_DATA,
+ hdev, data, len);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlocked;
+ }
- switch (key->type) {
- case MGMT_LTK_UNAUTHENTICATED:
- authenticated = 0x00;
- type = key->initiator ? SMP_LTK : SMP_LTK_RESPONDER;
- break;
- case MGMT_LTK_AUTHENTICATED:
- authenticated = 0x01;
- type = key->initiator ? SMP_LTK : SMP_LTK_RESPONDER;
- break;
- case MGMT_LTK_P256_UNAUTH:
- authenticated = 0x00;
- type = SMP_LTK_P256;
- break;
- case MGMT_LTK_P256_AUTH:
- authenticated = 0x01;
- type = SMP_LTK_P256;
- break;
- case MGMT_LTK_P256_DEBUG:
- authenticated = 0x00;
- type = SMP_LTK_P256_DEBUG;
- fallthrough;
- default:
- continue;
- }
+ hci_req_init(&req, hdev);
- hci_add_ltk(hdev, &key->addr.bdaddr,
- le_addr_type(key->addr.type), type, authenticated,
- key->val, key->enc_size, key->ediv, key->rand);
- }
+ memset(&adv, 0, sizeof(adv));
+ memcpy(adv.data, cp->data, len);
+ adv.length = len;
- err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS, 0,
- NULL, 0);
+ hci_req_add(&req, HCI_OP_LE_SET_ADV_DATA, sizeof(adv), &adv);
+
+ err = hci_req_run(&req, set_advertising_data_complete);
+ if (err < 0)
+ mgmt_pending_remove(cmd);
+unlocked:
hci_dev_unlock(hdev);
return err;
}
-static void get_conn_info_complete(struct hci_dev *hdev, void *data, int err)
+/* Adv White List feature */
+static void add_white_list_complete(struct hci_dev *hdev, u8 status, u16 opcode)
{
- struct mgmt_pending_cmd *cmd = data;
- struct hci_conn *conn = cmd->user_data;
- struct mgmt_cp_get_conn_info *cp = cmd->param;
- struct mgmt_rp_get_conn_info rp;
- u8 status;
+ struct mgmt_cp_add_dev_white_list *cp;
+ struct mgmt_pending_cmd *cmd;
- bt_dev_dbg(hdev, "err %d", err);
+ BT_DBG("status 0x%02x", status);
- memcpy(&rp.addr, &cp->addr.bdaddr, sizeof(rp.addr));
+ hci_dev_lock(hdev);
- status = mgmt_status(err);
- if (status == MGMT_STATUS_SUCCESS) {
- rp.rssi = conn->rssi;
- rp.tx_power = conn->tx_power;
- rp.max_tx_power = conn->max_tx_power;
- } else {
- rp.rssi = HCI_RSSI_INVALID;
- rp.tx_power = HCI_TX_POWER_INVALID;
- rp.max_tx_power = HCI_TX_POWER_INVALID;
- }
+ cmd = pending_find(MGMT_OP_ADD_DEV_WHITE_LIST, hdev);
+ if (!cmd)
+ goto unlock;
- mgmt_cmd_complete(cmd->sk, cmd->index, MGMT_OP_GET_CONN_INFO, status,
- &rp, sizeof(rp));
+ cp = cmd->param;
- mgmt_pending_free(cmd);
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_ADD_DEV_WHITE_LIST,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id,
+ MGMT_OP_ADD_DEV_WHITE_LIST, 0, cp, sizeof(*cp));
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
}
-static int get_conn_info_sync(struct hci_dev *hdev, void *data)
+static int add_white_list(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
{
- struct mgmt_pending_cmd *cmd = data;
- struct mgmt_cp_get_conn_info *cp = cmd->param;
- struct hci_conn *conn;
+ struct mgmt_pending_cmd *cmd;
+ struct mgmt_cp_add_dev_white_list *cp = data;
+ struct hci_request req;
int err;
- __le16 handle;
- /* Make sure we are still connected */
- if (cp->addr.type == BDADDR_BREDR)
- conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK,
- &cp->addr.bdaddr);
- else
- conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr);
+ BT_DBG("%s", hdev->name);
- if (!conn || conn->state != BT_CONNECTED)
- return MGMT_STATUS_NOT_CONNECTED;
+ if (!lmp_le_capable(hdev))
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_DEV_WHITE_LIST,
+ MGMT_STATUS_NOT_SUPPORTED);
- cmd->user_data = conn;
- handle = cpu_to_le16(conn->handle);
+ if (!hdev_is_powered(hdev))
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_DEV_WHITE_LIST,
+ MGMT_STATUS_REJECTED);
- /* Refresh RSSI each time */
- err = hci_read_rssi_sync(hdev, handle);
+ hci_dev_lock(hdev);
- /* For LE links TX power does not change thus we don't need to
- * query for it once value is known.
- */
- if (!err && (!bdaddr_type_is_le(cp->addr.type) ||
- conn->tx_power == HCI_TX_POWER_INVALID))
- err = hci_read_tx_power_sync(hdev, handle, 0x00);
+ if (pending_find(MGMT_OP_ADD_DEV_WHITE_LIST, hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_DEV_WHITE_LIST,
+ MGMT_STATUS_BUSY);
+ goto unlocked;
+ }
- /* Max TX power needs to be read only once per connection */
- if (!err && conn->max_tx_power == HCI_TX_POWER_INVALID)
- err = hci_read_tx_power_sync(hdev, handle, 0x01);
+ cmd = mgmt_pending_add(sk, MGMT_OP_ADD_DEV_WHITE_LIST, hdev, data, len);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlocked;
+ }
+
+ hci_req_init(&req, hdev);
+
+ hci_req_add(&req, HCI_OP_LE_ADD_TO_WHITE_LIST, sizeof(*cp), cp);
+
+ err = hci_req_run(&req, add_white_list_complete);
+ if (err < 0) {
+ mgmt_pending_remove(cmd);
+ goto unlocked;
+ }
+
+unlocked:
+ hci_dev_unlock(hdev);
return err;
}
-static int get_conn_info(struct sock *sk, struct hci_dev *hdev, void *data,
- u16 len)
+static void remove_from_white_list_complete(struct hci_dev *hdev,
+ u8 status, u16 opcode)
{
- struct mgmt_cp_get_conn_info *cp = data;
- struct mgmt_rp_get_conn_info rp;
- struct hci_conn *conn;
- unsigned long conn_info_age;
- int err = 0;
-
- bt_dev_dbg(hdev, "sock %p", sk);
-
- memset(&rp, 0, sizeof(rp));
- bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr);
- rp.addr.type = cp->addr.type;
+ struct mgmt_cp_remove_dev_from_white_list *cp;
+ struct mgmt_pending_cmd *cmd;
- if (!bdaddr_type_is_valid(cp->addr.type))
- return mgmt_cmd_complete(sk, hdev->id, MGMT_OP_GET_CONN_INFO,
- MGMT_STATUS_INVALID_PARAMS,
- &rp, sizeof(rp));
+ BT_DBG("status 0x%02x", status);
hci_dev_lock(hdev);
- if (!hdev_is_powered(hdev)) {
- err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_GET_CONN_INFO,
- MGMT_STATUS_NOT_POWERED, &rp,
- sizeof(rp));
+ cmd = pending_find(MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST, hdev);
+ if (!cmd)
goto unlock;
- }
- if (cp->addr.type == BDADDR_BREDR)
- conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK,
- &cp->addr.bdaddr);
- else
- conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr);
+ cp = cmd->param;
+
+ if (status)
+ mgmt_cmd_status(cmd->sk, hdev->id,
+ MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id,
+ MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST, 0,
+ cp, sizeof(*cp));
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static int remove_from_white_list(struct sock *sk, struct hci_dev *hdev,
+ void *data, u16 len)
+{
+ struct mgmt_pending_cmd *cmd;
+ struct mgmt_cp_remove_dev_from_white_list *cp = data;
+ 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_REMOVE_DEV_FROM_WHITE_LIST,
+ MGMT_STATUS_NOT_SUPPORTED);
+
+ if (!hdev_is_powered(hdev))
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_REMOVE_DEV_FROM_WHITE_LIST,
+ MGMT_STATUS_REJECTED);
+
+ hci_dev_lock(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_LE_READ_MAXIMUM_DATA_LENGTH,
+ MGMT_STATUS_BUSY);
+ goto unlock;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_LE_READ_MAXIMUM_DATA_LENGTH,
+ hdev, data, len);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ err = hci_send_cmd(hdev, HCI_OP_LE_READ_MAX_DATA_LEN, 0, NULL);
+ if (err < 0)
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+ return err;
+}
+
+void mgmt_le_write_host_suggested_data_length_complete(struct hci_dev *hdev,
+ u8 status)
+{
+ struct mgmt_pending_cmd *cmd;
+
+ BT_DBG("status 0x%02x", status);
+
+ hci_dev_lock(hdev);
+
+ 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_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
+ mgmt_status(status));
+ else
+ mgmt_cmd_complete(cmd->sk, hdev->id,
+ MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
+ 0, NULL, 0);
+
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+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 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("Write host suggested data length request for %s", hdev->name);
+
+ hci_dev_lock(hdev);
+
+ 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;
+ }
+
+ 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_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH, hdev)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
+ MGMT_STATUS_BUSY);
+ goto unlock;
+ }
+
+ cmd = mgmt_pending_add(sk, MGMT_OP_LE_WRITE_HOST_SUGGESTED_DATA_LENGTH,
+ hdev, data, len);
+ if (!cmd) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ hci_data.tx_len = cp->def_tx_octets;
+ hci_data.tx_time = cp->def_tx_time;
+
+ err = hci_send_cmd(hdev, HCI_OP_LE_WRITE_DEF_DATA_LEN,
+ sizeof(hci_data), &hci_data);
+ if (err < 0)
+ mgmt_pending_remove(cmd);
+
+unlock:
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+#endif /* TIZEN_BT */
+
+static bool ltk_is_valid(struct mgmt_ltk_info *key)
+{
+ if (key->initiator != 0x00 && key->initiator != 0x01)
+ return false;
+
+ switch (key->addr.type) {
+ case BDADDR_LE_PUBLIC:
+ return true;
+
+ case BDADDR_LE_RANDOM:
+ /* Two most significant bits shall be set */
+ if ((key->addr.bdaddr.b[5] & 0xc0) != 0xc0)
+ return false;
+ return true;
+ }
+
+ return false;
+}
+
+static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
+ void *cp_data, u16 len)
+{
+ struct mgmt_cp_load_long_term_keys *cp = cp_data;
+ const u16 max_key_count = ((U16_MAX - sizeof(*cp)) /
+ sizeof(struct mgmt_ltk_info));
+ u16 key_count, expected_len;
+ int i, err;
+
+ bt_dev_dbg(hdev, "sock %p", sk);
+
+ if (!lmp_le_capable(hdev))
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS,
+ MGMT_STATUS_NOT_SUPPORTED);
+
+ key_count = __le16_to_cpu(cp->key_count);
+ if (key_count > max_key_count) {
+ bt_dev_err(hdev, "load_ltks: too big key_count value %u",
+ key_count);
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS,
+ MGMT_STATUS_INVALID_PARAMS);
+ }
+
+ expected_len = struct_size(cp, keys, key_count);
+ if (expected_len != len) {
+ bt_dev_err(hdev, "load_keys: expected %u bytes, got %u bytes",
+ expected_len, len);
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS,
+ MGMT_STATUS_INVALID_PARAMS);
+ }
+
+ bt_dev_dbg(hdev, "key_count %u", key_count);
+
+ for (i = 0; i < key_count; i++) {
+ struct mgmt_ltk_info *key = &cp->keys[i];
+
+ if (!ltk_is_valid(key))
+ return mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LOAD_LONG_TERM_KEYS,
+ MGMT_STATUS_INVALID_PARAMS);
+ }
+
+ hci_dev_lock(hdev);
+
+ hci_smp_ltks_clear(hdev);
+
+ for (i = 0; i < key_count; i++) {
+ struct mgmt_ltk_info *key = &cp->keys[i];
+ u8 type, authenticated;
+
+ if (hci_is_blocked_key(hdev,
+ HCI_BLOCKED_KEY_TYPE_LTK,
+ key->val)) {
+ bt_dev_warn(hdev, "Skipping blocked LTK for %pMR",
+ &key->addr.bdaddr);
+ continue;
+ }
+
+ switch (key->type) {
+ case MGMT_LTK_UNAUTHENTICATED:
+ authenticated = 0x00;
+ type = key->initiator ? SMP_LTK : SMP_LTK_RESPONDER;
+ break;
+ case MGMT_LTK_AUTHENTICATED:
+ authenticated = 0x01;
+ type = key->initiator ? SMP_LTK : SMP_LTK_RESPONDER;
+ break;
+ case MGMT_LTK_P256_UNAUTH:
+ authenticated = 0x00;
+ type = SMP_LTK_P256;
+ break;
+ case MGMT_LTK_P256_AUTH:
+ authenticated = 0x01;
+ type = SMP_LTK_P256;
+ break;
+ case MGMT_LTK_P256_DEBUG:
+ authenticated = 0x00;
+ type = SMP_LTK_P256_DEBUG;
+ fallthrough;
+ default:
+ continue;
+ }
+
+ hci_add_ltk(hdev, &key->addr.bdaddr,
+ le_addr_type(key->addr.type), type, authenticated,
+ key->val, key->enc_size, key->ediv, key->rand);
+ }
+
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS, 0,
+ NULL, 0);
+
+ hci_dev_unlock(hdev);
+
+ return err;
+}
+
+static void get_conn_info_complete(struct hci_dev *hdev, void *data, int err)
+{
+ struct mgmt_pending_cmd *cmd = data;
+ struct hci_conn *conn = cmd->user_data;
+ struct mgmt_cp_get_conn_info *cp = cmd->param;
+ struct mgmt_rp_get_conn_info rp;
+ u8 status;
+
+ bt_dev_dbg(hdev, "err %d", err);
+
+ memcpy(&rp.addr, &cp->addr.bdaddr, sizeof(rp.addr));
+
+ status = mgmt_status(err);
+ if (status == MGMT_STATUS_SUCCESS) {
+ rp.rssi = conn->rssi;
+ rp.tx_power = conn->tx_power;
+ rp.max_tx_power = conn->max_tx_power;
+ } else {
+ rp.rssi = HCI_RSSI_INVALID;
+ rp.tx_power = HCI_TX_POWER_INVALID;
+ rp.max_tx_power = HCI_TX_POWER_INVALID;
+ }
+
+ mgmt_cmd_complete(cmd->sk, cmd->index, MGMT_OP_GET_CONN_INFO, status,
+ &rp, sizeof(rp));
+
+ mgmt_pending_free(cmd);
+}
+
+static int get_conn_info_sync(struct hci_dev *hdev, void *data)
+{
+ struct mgmt_pending_cmd *cmd = data;
+ struct mgmt_cp_get_conn_info *cp = cmd->param;
+ struct hci_conn *conn;
+ int err;
+ __le16 handle;
+
+ /* Make sure we are still connected */
+ if (cp->addr.type == BDADDR_BREDR)
+ conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK,
+ &cp->addr.bdaddr);
+ else
+ conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr);
+
+ if (!conn || conn->state != BT_CONNECTED)
+ return MGMT_STATUS_NOT_CONNECTED;
+
+ cmd->user_data = conn;
+ handle = cpu_to_le16(conn->handle);
+
+ /* Refresh RSSI each time */
+ err = hci_read_rssi_sync(hdev, handle);
+
+ /* For LE links TX power does not change thus we don't need to
+ * query for it once value is known.
+ */
+ if (!err && (!bdaddr_type_is_le(cp->addr.type) ||
+ conn->tx_power == HCI_TX_POWER_INVALID))
+ err = hci_read_tx_power_sync(hdev, handle, 0x00);
+
+ /* Max TX power needs to be read only once per connection */
+ if (!err && conn->max_tx_power == HCI_TX_POWER_INVALID)
+ err = hci_read_tx_power_sync(hdev, handle, 0x01);
+
+ return err;
+}
+
+static int get_conn_info(struct sock *sk, struct hci_dev *hdev, void *data,
+ u16 len)
+{
+ struct mgmt_cp_get_conn_info *cp = data;
+ struct mgmt_rp_get_conn_info rp;
+ struct hci_conn *conn;
+ unsigned long conn_info_age;
+ int err = 0;
+
+ bt_dev_dbg(hdev, "sock %p", sk);
+
+ 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 mgmt_cmd_complete(sk, hdev->id, MGMT_OP_GET_CONN_INFO,
+ MGMT_STATUS_INVALID_PARAMS,
+ &rp, sizeof(rp));
+
+ hci_dev_lock(hdev);
+
+ if (!hdev_is_powered(hdev)) {
+ err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_GET_CONN_INFO,
+ MGMT_STATUS_NOT_POWERED, &rp,
+ sizeof(rp));
+ goto unlock;
+ }
+
+ if (cp->addr.type == BDADDR_BREDR)
+ conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK,
+ &cp->addr.bdaddr);
+ else
+ conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr);
if (!conn || conn->state != BT_CONNECTED) {
err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_GET_CONN_INFO,
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)
{
* extra parameters we don't know about will be ignored in this request.
*/
if (data_len < MGMT_ADD_EXT_ADV_PARAMS_MIN_SIZE)
- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_EXT_ADV_PARAMS,
MGMT_STATUS_INVALID_PARAMS);
flags = __le32_to_cpu(cp->flags);
{ add_ext_adv_data, MGMT_ADD_EXT_ADV_DATA_SIZE,
HCI_MGMT_VAR_LEN },
{ add_adv_patterns_monitor_rssi,
- MGMT_ADD_ADV_PATTERNS_MONITOR_RSSI_SIZE },
+ MGMT_ADD_ADV_PATTERNS_MONITOR_RSSI_SIZE,
+ HCI_MGMT_VAR_LEN },
{ set_mesh, MGMT_SET_MESH_RECEIVER_SIZE,
HCI_MGMT_VAR_LEN },
{ mesh_features, MGMT_MESH_READ_FEATURES_SIZE },
{ mesh_send_cancel, MGMT_MESH_SEND_CANCEL_SIZE },
};
+#ifdef TIZEN_BT
+static const struct hci_mgmt_handler tizen_mgmt_handlers[] = {
+ { NULL }, /* 0x0000 (no command) */
+ { set_advertising_params, MGMT_SET_ADVERTISING_PARAMS_SIZE },
+ { set_advertising_data, MGMT_SET_ADV_MIN_APP_DATA_SIZE,
+ HCI_MGMT_VAR_LEN },
+ { set_scan_rsp_data, MGMT_SET_SCAN_RSP_MIN_APP_DATA_SIZE,
+ HCI_MGMT_VAR_LEN },
+ { 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 },
+};
+#endif
+
void mgmt_index_added(struct hci_dev *hdev)
{
struct mgmt_ev_ext_index ev;
.channel = HCI_CHANNEL_CONTROL,
.handler_count = ARRAY_SIZE(mgmt_handlers),
.handlers = mgmt_handlers,
+#ifdef TIZEN_BT
+ .tizen_handler_count = ARRAY_SIZE(tizen_mgmt_handlers),
+ .tizen_handlers = tizen_mgmt_handlers,
+#endif
.hdev_init = mgmt_init_hdev,
};