Bluetooth: Write host suggested default le data length
[platform/kernel/linux-starfive.git] / net / bluetooth / mgmt.c
index 88563d7..f4a0158 100644 (file)
@@ -34,6 +34,7 @@
 #include <net/bluetooth/mgmt.h>
 #ifdef TIZEN_BT
 #include <net/bluetooth/mgmt_tizen.h>
+#include <net/bluetooth/sco.h>
 #endif
 
 #include "hci_request.h"
@@ -7028,9 +7029,14 @@ static int set_privacy(struct sock *sk, struct hci_dev *hdev, void *cp_data,
                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);
 
@@ -8823,6 +8829,92 @@ static int le_set_scan_params(struct sock *sk, struct hci_dev *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;
@@ -8835,6 +8927,366 @@ 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)
@@ -9797,6 +10249,48 @@ int mgmt_le_conn_updated(struct hci_dev *hdev, bdaddr_t *bdaddr,
        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,
@@ -11126,6 +11620,15 @@ static const struct hci_mgmt_handler tizen_mgmt_handlers[] = {
        { 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