From a94b6122f4f7a44dc1d6e6a5dfd2533cad7a3abf Mon Sep 17 00:00:00 2001 From: Brian Gix Date: Thu, 23 Feb 2012 16:07:10 -0800 Subject: [PATCH] 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 --- include/net/bluetooth/bluetooth.h | 37 ++++++++++ include/net/bluetooth/hci_core.h | 5 ++ include/net/bluetooth/l2cap.h | 1 + net/bluetooth/hci_conn.c | 113 +++++++++++++++++------------- net/bluetooth/hci_event.c | 9 +-- net/bluetooth/l2cap_core.c | 56 +++++++++------ net/bluetooth/l2cap_sock.c | 64 +++++++++++++++++ net/bluetooth/mgmt.c | 4 +- 8 files changed, 214 insertions(+), 75 deletions(-) diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index 7e9f5f4f374..d218fab0c02 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -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 { diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 5053bd88770..574929309a0 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -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); diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index b903c5c608d..a098f3e2512 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -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); diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index d60bf00bbb2..25b559b19bd 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -44,33 +44,77 @@ #include #include +#include -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) { diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 598c3106eb7..395a95c54a4 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -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); diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 37ee9b1d15a..9f9f17d2f31 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -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; } diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 8eb1e16bf2e..33d5d22e24d 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -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; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index e15609c30be..80f4bd6d3fa 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -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)