forked from rubenslte/android_kernel_samsung_msm8226
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>
This commit is contained in:
@@ -99,6 +99,28 @@ struct bt_power {
|
||||
*/
|
||||
#define BT_AMP_POLICY_PREFER_AMP 2
|
||||
|
||||
#define BT_LE_PARAMS 100
|
||||
|
||||
#define BT_LE_SCAN_WINDOW_MIN 0x0004
|
||||
#define BT_LE_SCAN_WINDOW_MAX 0x4000
|
||||
#define BT_LE_SCAN_WINDOW_DEF 0x0004
|
||||
|
||||
#define BT_LE_SCAN_INTERVAL_MIN 0x0004
|
||||
#define BT_LE_SCAN_INTERVAL_MAX 0x4000
|
||||
#define BT_LE_SCAN_INTERVAL_DEF 0x0008
|
||||
|
||||
#define BT_LE_CONN_INTERVAL_MIN 0x0006
|
||||
#define BT_LE_CONN_INTERVAL_MAX 0x0C80
|
||||
#define BT_LE_CONN_INTERVAL_MIN_DEF 0x0008
|
||||
#define BT_LE_CONN_INTERVAL_MAX_DEF 0x0100
|
||||
|
||||
#define BT_LE_LATENCY_MAX 0x01F4
|
||||
#define BT_LE_LATENCY_DEF 0x0000
|
||||
|
||||
#define BT_LE_SUP_TO_MIN 0x000A
|
||||
#define BT_LE_SUP_TO_MAX 0x0C80
|
||||
#define BT_LE_SUP_TO_DEFAULT 0X03E8
|
||||
|
||||
#define BT_INFO(fmt, arg...) printk(KERN_INFO "Bluetooth: " fmt "\n" , ## arg)
|
||||
#define BT_ERR(fmt, arg...) printk(KERN_ERR "%s: " fmt "\n" , __func__ , ## arg)
|
||||
#define BT_DBG(fmt, arg...) pr_debug("%s: " fmt "\n" , __func__ , ## arg)
|
||||
@@ -142,6 +164,20 @@ bdaddr_t *strtoba(char *str);
|
||||
|
||||
#define bt_sk(__sk) ((struct bt_sock *) __sk)
|
||||
|
||||
struct bt_le_params {
|
||||
__u8 prohibit_remote_chg;
|
||||
__u8 filter_policy;
|
||||
__u16 scan_interval;
|
||||
__u16 scan_window;
|
||||
__u16 interval_min;
|
||||
__u16 interval_max;
|
||||
__u16 latency;
|
||||
__u16 supervision_timeout;
|
||||
__u16 min_ce_len;
|
||||
__u16 max_ce_len;
|
||||
__u16 conn_timeout;
|
||||
};
|
||||
|
||||
struct bt_sock {
|
||||
struct sock sk;
|
||||
bdaddr_t src;
|
||||
@@ -149,6 +185,7 @@ struct bt_sock {
|
||||
struct list_head accept_q;
|
||||
struct sock *parent;
|
||||
u32 defer_setup;
|
||||
struct bt_le_params le_params;
|
||||
};
|
||||
|
||||
struct bt_sock_list {
|
||||
|
||||
@@ -304,6 +304,7 @@ struct hci_conn {
|
||||
__u8 auth_initiator;
|
||||
__u8 power_save;
|
||||
__u16 disc_timeout;
|
||||
__u16 conn_timeout;
|
||||
unsigned long pend;
|
||||
|
||||
__u8 remote_cap;
|
||||
@@ -597,6 +598,10 @@ void hci_chan_modify(struct hci_chan *chan,
|
||||
struct hci_conn *hci_connect(struct hci_dev *hdev, int type,
|
||||
__u16 pkt_type, bdaddr_t *dst,
|
||||
__u8 sec_level, __u8 auth_type);
|
||||
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);
|
||||
int hci_conn_check_link_mode(struct hci_conn *conn);
|
||||
int hci_conn_security(struct hci_conn *conn, __u8 sec_level, __u8 auth_type);
|
||||
int hci_conn_change_link_key(struct hci_conn *conn);
|
||||
|
||||
@@ -685,6 +685,7 @@ int l2cap_strm_tx(struct sock *sk, struct sk_buff_head *skbs);
|
||||
int l2cap_ertm_tx(struct sock *sk, struct bt_l2cap_control *control,
|
||||
struct sk_buff_head *skbs, u8 event);
|
||||
|
||||
int l2cap_sock_le_params_valid(struct bt_le_params *le_params);
|
||||
void l2cap_sock_set_timer(struct sock *sk, long timeout);
|
||||
void l2cap_sock_clear_timer(struct sock *sk);
|
||||
void __l2cap_sock_close(struct sock *sk, int reason);
|
||||
|
||||
+64
-49
@@ -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 *hci_connect(struct hci_dev *hdev, int type,
|
||||
{
|
||||
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) {
|
||||
|
||||
@@ -1368,6 +1368,7 @@ static void hci_cs_le_create_conn(struct hci_dev *hdev, __u8 status)
|
||||
{
|
||||
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 @@ static void hci_cs_le_create_conn(struct hci_dev *hdev, __u8 status)
|
||||
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);
|
||||
|
||||
+36
-20
@@ -1277,8 +1277,9 @@ int l2cap_do_connect(struct sock *sk)
|
||||
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 @@ int l2cap_do_connect(struct sock *sk)
|
||||
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 @@ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn,
|
||||
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 @@ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,23 @@ void l2cap_sock_clear_timer(struct sock *sk)
|
||||
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 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch
|
||||
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 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, ch
|
||||
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 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, ch
|
||||
|
||||
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;
|
||||
|
||||
@@ -1623,8 +1623,8 @@ static int pair_device(struct sock *sk, u16 index, unsigned char *data, u16 len)
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user