#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);
return err;
}
-void mgmt_rssi_alert_evt(struct hci_dev *hdev, struct sk_buff *skb)
+void mgmt_rssi_alert_evt(struct hci_dev *hdev, u16 conn_handle,
+ s8 alert_type, s8 rssi_dbm)
{
- struct hci_ev_vendor_specific_rssi_alert *ev = (void *)skb->data;
struct mgmt_ev_vendor_specific_rssi_alert mgmt_ev;
struct hci_conn *conn;
BT_DBG("RSSI alert [%2.2X %2.2X %2.2X]",
- ev->conn_handle, ev->alert_type, ev->rssi_dbm);
+ conn_handle, alert_type, rssi_dbm);
- conn = hci_conn_hash_lookup_handle(hdev, ev->conn_handle);
+ conn = hci_conn_hash_lookup_handle(hdev, conn_handle);
if (!conn) {
BT_ERR("RSSI alert Error: Device not found for handle");
else
mgmt_ev.link_type = 0x00;
- mgmt_ev.alert_type = ev->alert_type;
- mgmt_ev.rssi_dbm = ev->rssi_dbm;
+ 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),
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)
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,
{ 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