Bluetooth: Add sockopts for LE Connection Params
Various LE profiles require very specific LE parameters for
both establishing and maintaining LE connections to remote
peripherals. These LE only parameters are encapsulated into
a single sockopts structure, and may be passed from user space.
CRs-Fixed: 335971
Change-Id: I408edb97ab0fa9717c7d3fe5fc8ad6ac179a2fff
Signed-off-by: Brian Gix <bgix@codeaurora.org>
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index d60bf00..25b559b1 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -44,33 +44,77 @@
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/l2cap.h>
-static void hci_le_connect(struct hci_conn *conn)
+struct hci_conn *hci_le_connect(struct hci_dev *hdev, __u16 pkt_type,
+ bdaddr_t *dst, __u8 sec_level, __u8 auth_type,
+ struct bt_le_params *le_params)
{
- struct hci_dev *hdev = conn->hdev;
+ struct hci_conn *le;
struct hci_cp_le_create_conn cp;
+ struct adv_entry *entry;
+ struct link_key *key;
- BT_DBG("%p", conn);
+ BT_DBG("%p", hdev);
- conn->state = BT_CONNECT;
- conn->out = 1;
- conn->link_mode |= HCI_LM_MASTER;
- conn->sec_level = BT_SECURITY_LOW;
- conn->type = LE_LINK;
+ le = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst);
+ if (le) {
+ hci_conn_hold(le);
+ return le;
+ }
+
+ key = hci_find_link_key_type(hdev, dst, KEY_TYPE_LTK);
+ if (!key) {
+ entry = hci_find_adv_entry(hdev, dst);
+ if (entry)
+ le = hci_le_conn_add(hdev, dst,
+ entry->bdaddr_type);
+ else
+ le = hci_le_conn_add(hdev, dst, 0);
+ } else {
+ le = hci_le_conn_add(hdev, dst, key->addr_type);
+ }
+
+ if (!le)
+ return ERR_PTR(-ENOMEM);
+
+ hci_conn_hold(le);
+
+ le->state = BT_CONNECT;
+ le->out = 1;
+ le->link_mode |= HCI_LM_MASTER;
+ le->sec_level = BT_SECURITY_LOW;
+ le->type = LE_LINK;
memset(&cp, 0, sizeof(cp));
- cp.scan_interval = cpu_to_le16(0x0004);
- cp.scan_window = cpu_to_le16(0x0004);
- bacpy(&cp.peer_addr, &conn->dst);
- cp.peer_addr_type = conn->dst_type;
- cp.conn_interval_min = cpu_to_le16(0x0008);
- cp.conn_interval_max = cpu_to_le16(0x0100);
- cp.supervision_timeout = cpu_to_le16(1000);
- cp.min_ce_len = cpu_to_le16(0x0001);
- cp.max_ce_len = cpu_to_le16(0x0001);
+ if (l2cap_sock_le_params_valid(le_params)) {
+ cp.supervision_timeout =
+ cpu_to_le16(le_params->supervision_timeout);
+ cp.scan_interval = cpu_to_le16(le_params->scan_interval);
+ cp.scan_window = cpu_to_le16(le_params->scan_window);
+ cp.conn_interval_min = cpu_to_le16(le_params->interval_min);
+ cp.conn_interval_max = cpu_to_le16(le_params->interval_max);
+ cp.conn_latency = cpu_to_le16(le_params->latency);
+ cp.min_ce_len = cpu_to_le16(le_params->min_ce_len);
+ cp.max_ce_len = cpu_to_le16(le_params->max_ce_len);
+ le->conn_timeout = le_params->conn_timeout;
+ } else {
+ cp.supervision_timeout = cpu_to_le16(BT_LE_SUP_TO_DEFAULT);
+ cp.scan_interval = cpu_to_le16(BT_LE_SCAN_INTERVAL_DEF);
+ cp.scan_window = cpu_to_le16(BT_LE_SCAN_WINDOW_DEF);
+ cp.conn_interval_min = cpu_to_le16(BT_LE_CONN_INTERVAL_MIN_DEF);
+ cp.conn_interval_max = cpu_to_le16(BT_LE_CONN_INTERVAL_MAX_DEF);
+ cp.conn_latency = cpu_to_le16(BT_LE_LATENCY_DEF);
+ le->conn_timeout = 5;
+ }
+ bacpy(&cp.peer_addr, &le->dst);
+ cp.peer_addr_type = le->dst_type;
hci_send_cmd(hdev, HCI_OP_LE_CREATE_CONN, sizeof(cp), &cp);
+
+ return le;
}
+EXPORT_SYMBOL(hci_le_connect);
static void hci_le_connect_cancel(struct hci_conn *conn)
{
@@ -685,41 +729,12 @@
{
struct hci_conn *acl;
struct hci_conn *sco;
- struct hci_conn *le;
BT_DBG("%s dst %s", hdev->name, batostr(dst));
- if (type == LE_LINK) {
- struct adv_entry *entry;
- struct link_key *key;
-
- le = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst);
- if (le) {
- hci_conn_hold(le);
- return le;
- }
-
- key = hci_find_link_key_type(hdev, dst, KEY_TYPE_LTK);
- if (!key) {
- entry = hci_find_adv_entry(hdev, dst);
- if (entry)
- le = hci_le_conn_add(hdev, dst,
- entry->bdaddr_type);
- else
- le = hci_le_conn_add(hdev, dst, 0);
- } else {
- le = hci_le_conn_add(hdev, dst, key->addr_type);
- }
-
- if (!le)
- return ERR_PTR(-ENOMEM);
-
- hci_le_connect(le);
-
- hci_conn_hold(le);
-
- return le;
- }
+ if (type == LE_LINK)
+ return hci_le_connect(hdev, pkt_type, dst, sec_level,
+ auth_type, NULL);
acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst);
if (!acl) {
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 598c310..395a95c 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -1368,6 +1368,7 @@
{
struct hci_cp_le_create_conn *cp;
struct hci_conn *conn;
+ unsigned long exp = msecs_to_jiffies(5000);
BT_DBG("%s status 0x%x", hdev->name, status);
@@ -1396,11 +1397,11 @@
conn->out = 1;
else
BT_ERR("No memory for new connection");
- }
+ } else
+ exp = msecs_to_jiffies(conn->conn_timeout * 1000);
- if (conn)
- mod_timer(&conn->disc_timer,
- jiffies + msecs_to_jiffies(5000));
+ if (conn && exp)
+ mod_timer(&conn->disc_timer, jiffies + exp);
}
hci_dev_unlock(hdev);
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 37ee9b1..9f9f17d 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -1277,8 +1277,9 @@
conn = hcon->l2cap_data;
} else {
if (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA)
- hcon = hci_connect(hdev, LE_LINK, 0, dst,
- l2cap_pi(sk)->sec_level, auth_type);
+ hcon = hci_le_connect(hdev, 0, dst,
+ l2cap_pi(sk)->sec_level, auth_type,
+ &bt_sk(sk)->le_params);
else
hcon = hci_connect(hdev, ACL_LINK, 0, dst,
l2cap_pi(sk)->sec_level, auth_type);
@@ -1308,7 +1309,17 @@
sk->sk_state_change(sk);
} else {
sk->sk_state = BT_CONNECT;
- l2cap_sock_set_timer(sk, sk->sk_sndtimeo);
+ /* If we have valid LE Params, let timeout override default */
+ if (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA &&
+ l2cap_sock_le_params_valid(&bt_sk(sk)->le_params)) {
+ u16 timeout = bt_sk(sk)->le_params.conn_timeout;
+
+ if (timeout)
+ l2cap_sock_set_timer(sk,
+ msecs_to_jiffies(timeout*1000));
+ } else
+ l2cap_sock_set_timer(sk, sk->sk_sndtimeo);
+
sk->sk_state_change(sk);
if (hcon->state == BT_CONNECTED) {
@@ -5668,7 +5679,8 @@
struct hci_conn *hcon = conn->hcon;
struct l2cap_conn_param_update_req *req;
struct l2cap_conn_param_update_rsp rsp;
- u16 min, max, latency, to_multiplier, cmd_len;
+ struct sock *sk;
+ u16 min, max, latency, timeout, cmd_len;
int err;
if (!(hcon->link_mode & HCI_LM_MASTER))
@@ -5678,28 +5690,32 @@
if (cmd_len != sizeof(struct l2cap_conn_param_update_req))
return -EPROTO;
- req = (struct l2cap_conn_param_update_req *) data;
- min = __le16_to_cpu(req->min);
- max = __le16_to_cpu(req->max);
- latency = __le16_to_cpu(req->latency);
- to_multiplier = __le16_to_cpu(req->to_multiplier);
-
- BT_DBG("min 0x%4.4x max 0x%4.4x latency: 0x%4.4x Timeout: 0x%4.4x",
- min, max, latency, to_multiplier);
-
memset(&rsp, 0, sizeof(rsp));
+ rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_REJECTED);
- err = l2cap_check_conn_param(min, max, latency, to_multiplier);
- if (err)
- rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_REJECTED);
- else
- rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_ACCEPTED);
+ sk = l2cap_find_sock_by_fixed_cid_and_dir(4, conn->src, conn->dst, 0);
+
+ if (sk && !bt_sk(sk)->le_params.prohibit_remote_chg) {
+ req = (struct l2cap_conn_param_update_req *) data;
+ min = __le16_to_cpu(req->min);
+ max = __le16_to_cpu(req->max);
+ latency = __le16_to_cpu(req->latency);
+ timeout = __le16_to_cpu(req->to_multiplier);
+
+ err = l2cap_check_conn_param(min, max, latency, timeout);
+ if (!err) {
+ rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_ACCEPTED);
+ hci_le_conn_update(hcon, min, max, latency, timeout);
+ bt_sk(sk)->le_params.interval_min = min;
+ bt_sk(sk)->le_params.interval_max = max;
+ bt_sk(sk)->le_params.latency = latency;
+ bt_sk(sk)->le_params.supervision_timeout = timeout;
+ }
+ }
l2cap_send_cmd(conn, cmd->ident, L2CAP_CONN_PARAM_UPDATE_RSP,
sizeof(rsp), &rsp);
- if (!err)
- hci_le_conn_update(hcon, min, max, latency, to_multiplier);
return 0;
}
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 8eb1e16..33d5d22 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -78,6 +78,23 @@
sk_stop_timer(sk, &sk->sk_timer);
}
+int l2cap_sock_le_params_valid(struct bt_le_params *le_params)
+{
+ if (!le_params || le_params->latency > BT_LE_LATENCY_MAX ||
+ le_params->scan_window > BT_LE_SCAN_WINDOW_MAX ||
+ le_params->scan_interval < BT_LE_SCAN_INTERVAL_MIN ||
+ le_params->scan_window > le_params->scan_interval ||
+ le_params->interval_min < BT_LE_CONN_INTERVAL_MIN ||
+ le_params->interval_max > BT_LE_CONN_INTERVAL_MAX ||
+ le_params->interval_min > le_params->interval_max ||
+ le_params->supervision_timeout < BT_LE_SUP_TO_MIN ||
+ le_params->supervision_timeout > BT_LE_SUP_TO_MAX) {
+ return 0;
+ }
+
+ return 1;
+}
+
static struct sock *__l2cap_get_sock_by_addr(__le16 psm, bdaddr_t *src)
{
struct sock *sk;
@@ -547,6 +564,17 @@
err = -EFAULT;
break;
+ case BT_LE_PARAMS:
+ if (l2cap_pi(sk)->scid != L2CAP_CID_LE_DATA) {
+ err = -EINVAL;
+ break;
+ }
+
+ if (copy_to_user(optval, (char *) &bt_sk(sk)->le_params,
+ sizeof(bt_sk(sk)->le_params)))
+ err = -EFAULT;
+ break;
+
default:
err = -ENOPROTOOPT;
break;
@@ -671,6 +699,7 @@
struct sock *sk = sock->sk;
struct bt_security sec;
struct bt_power pwr;
+ struct bt_le_params le_params;
struct l2cap_conn *conn;
int len, err = 0;
u32 opt;
@@ -786,6 +815,41 @@
break;
+ case BT_LE_PARAMS:
+ if (l2cap_pi(sk)->scid != L2CAP_CID_LE_DATA) {
+ err = -EINVAL;
+ break;
+ }
+
+ if (copy_from_user((char *) &le_params, optval,
+ sizeof(struct bt_le_params))) {
+ err = -EFAULT;
+ break;
+ }
+
+ conn = l2cap_pi(sk)->conn;
+ if (!conn || !conn->hcon ||
+ l2cap_pi(sk)->scid != L2CAP_CID_LE_DATA) {
+ memcpy(&bt_sk(sk)->le_params, &le_params,
+ sizeof(le_params));
+ break;
+ }
+
+ if (!conn->hcon->out ||
+ !l2cap_sock_le_params_valid(&le_params)) {
+ err = -EINVAL;
+ break;
+ }
+
+ memcpy(&bt_sk(sk)->le_params, &le_params, sizeof(le_params));
+
+ hci_le_conn_update(conn->hcon,
+ le_params.interval_min,
+ le_params.interval_max,
+ le_params.latency,
+ le_params.supervision_timeout);
+ break;
+
default:
err = -ENOPROTOOPT;
break;
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index e15609c..80f4bd6 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -1623,8 +1623,8 @@
entry = hci_find_adv_entry(hdev, &cp->bdaddr);
if (entry && entry->flags & 0x04) {
- conn = hci_connect(hdev, LE_LINK, 0, &cp->bdaddr, sec_level,
- auth_type);
+ conn = hci_le_connect(hdev, 0, &cp->bdaddr, sec_level,
+ auth_type, NULL);
} else {
/* ACL-SSP does not support io_cap 0x04 (KeyboadDisplay) */
if (io_cap == 0x04)