Hi,
This is V3 of patch set to enable bluetooth Low Energy traffic
over unencrypted links.
Changes from RFC V2
- Gustavo's review.
On Mon, Oct 25, 2010 at 8:21 AM, Ville Tervo <[email protected]> wrote:
> +static void l2cap_le_conn_ready(struct l2cap_conn *conn)
> +{
> + ? ? ? struct l2cap_chan_list *list = &conn->chan_list;
> + ? ? ? struct sock *parent, *uninitialized_var(sk);
> +
> + ? ? ? BT_DBG("");
> +
> + ? ? ? /* Check if we have socket listening on cid */
> + ? ? ? parent = l2cap_get_sock_by_cid(BT_LISTEN, 0x04, conn->src);
You can use L2CAP_CID_LE_DATA instead of 0x04.
Regards,
--
Anderson Lizardo
OpenBossa Labs - INdT
Manaus - Brazil
Hi,
On Mon, Oct 25, 2010 at 10:33:05PM +0200, ext Anderson Lizardo wrote:
> Hi,
>
> On Mon, Oct 25, 2010 at 8:21 AM, Ville Tervo <[email protected]> wrote:
> > diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> > index 0b1e460..0944c0c 100644
> > --- a/net/bluetooth/hci_conn.c
> > +++ b/net/bluetooth/hci_conn.c
> > @@ -45,6 +45,32 @@
> > ?#include <net/bluetooth/bluetooth.h>
> > ?#include <net/bluetooth/hci_core.h>
> >
> > +void hci_le_connect(struct hci_conn *conn)
>
> This function could be made static, right? (just noticed this now)
Yes. Thanks.
>
> > +{
> > + ? ? ? struct hci_dev *hdev = conn->hdev;
> > + ? ? ? struct hci_cp_le_create_conn cp;
> > +
> > + ? ? ? conn->state = BT_CONNECT;
> > + ? ? ? conn->out = 1;
> > +
> > + ? ? ? 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.conn_interval_min = cpu_to_le16(0x0008);
> > + ? ? ? cp.conn_interval_max = cpu_to_le16(0x0100);
> > + ? ? ? cp.supervision_timeout = cpu_to_le16(0x0064);
> > + ? ? ? cp.min_ce_len = cpu_to_le16(0x0001);
> > + ? ? ? cp.max_ce_len = cpu_to_le16(0x0001);
> > +
> > + ? ? ? hci_send_cmd(hdev, HCI_OP_LE_CREATE_CONN, sizeof(cp), &cp);
> > +}
--
Ville
Hi,
On Mon, Oct 25, 2010 at 8:21 AM, Ville Tervo <[email protected]> wrote:
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index 0b1e460..0944c0c 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -45,6 +45,32 @@
> ?#include <net/bluetooth/bluetooth.h>
> ?#include <net/bluetooth/hci_core.h>
>
> +void hci_le_connect(struct hci_conn *conn)
This function could be made static, right? (just noticed this now)
> +{
> + ? ? ? struct hci_dev *hdev = conn->hdev;
> + ? ? ? struct hci_cp_le_create_conn cp;
> +
> + ? ? ? conn->state = BT_CONNECT;
> + ? ? ? conn->out = 1;
> +
> + ? ? ? 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.conn_interval_min = cpu_to_le16(0x0008);
> + ? ? ? cp.conn_interval_max = cpu_to_le16(0x0100);
> + ? ? ? cp.supervision_timeout = cpu_to_le16(0x0064);
> + ? ? ? cp.min_ce_len = cpu_to_le16(0x0001);
> + ? ? ? cp.max_ce_len = cpu_to_le16(0x0001);
> +
> + ? ? ? hci_send_cmd(hdev, HCI_OP_LE_CREATE_CONN, sizeof(cp), &cp);
> +}
Regards,
--
Anderson Lizardo
OpenBossa Labs - INdT
Manaus - Brazil
Add support for LE server sockets.
Signed-off-by: Ville Tervo <[email protected]>
---
include/net/bluetooth/l2cap.h | 1 +
net/bluetooth/hci_event.c | 10 +++-
net/bluetooth/l2cap.c | 101 +++++++++++++++++++++++++++++++++++++++--
3 files changed, 105 insertions(+), 7 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index cc3a140..cba4423 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -38,6 +38,7 @@
#define L2CAP_DEFAULT_MAX_PDU_SIZE 1009 /* Sized for 3-DH5 packet */
#define L2CAP_DEFAULT_ACK_TO 200
#define L2CAP_LOCAL_BUSY_TRIES 12
+#define L2CAP_LE_DEFAULT_MTU 23
#define L2CAP_CONN_TIMEOUT (40000) /* 40 seconds */
#define L2CAP_INFO_TIMEOUT (4000) /* 4 seconds */
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index da23502..464d0cc 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -1914,8 +1914,14 @@ static inline void hci_le_conn_complete_evt(struct hci_dev *hdev, struct sk_buff
hci_dev_lock(hdev);
conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &ev->bdaddr);
- if (!conn)
- goto unlock;
+ if (!conn) {
+ conn = hci_conn_add(hdev, LE_LINK, &ev->bdaddr);
+ if (!conn) {
+ BT_ERR("No memory for new connection");
+ hci_dev_unlock(hdev);
+ return;
+ }
+ }
if (ev->status) {
hci_proto_connect_cfm(conn, ev->status);
diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
index 18643af..3913ba5 100644
--- a/net/bluetooth/l2cap.c
+++ b/net/bluetooth/l2cap.c
@@ -82,6 +82,8 @@ static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
static int l2cap_ertm_data_rcv(struct sock *sk, struct sk_buff *skb);
+static void l2cap_le_conn_ready(struct l2cap_conn *conn);
+
/* ---- L2CAP timers ---- */
static void l2cap_sock_timeout(unsigned long arg)
{
@@ -228,8 +230,16 @@ static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct so
l2cap_pi(sk)->conn = conn;
if (sk->sk_type == SOCK_SEQPACKET || sk->sk_type == SOCK_STREAM) {
- /* Alloc CID for connection-oriented socket */
- l2cap_pi(sk)->scid = l2cap_alloc_cid(l);
+ if (conn->hcon->type == LE_LINK) {
+ /* LE connection */
+ l2cap_pi(sk)->omtu = L2CAP_LE_DEFAULT_MTU;
+ l2cap_pi(sk)->scid = L2CAP_CID_LE_DATA;
+ l2cap_pi(sk)->dcid = L2CAP_CID_LE_DATA;
+ } else {
+ /* Alloc CID for connection-oriented socket */
+ l2cap_pi(sk)->scid = l2cap_alloc_cid(l);
+ l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU;
+ }
} else if (sk->sk_type == SOCK_DGRAM) {
/* Connectionless socket */
l2cap_pi(sk)->scid = L2CAP_CID_CONN_LESS;
@@ -612,6 +622,9 @@ static void l2cap_conn_ready(struct l2cap_conn *conn)
BT_DBG("conn %p", conn);
+ if (!conn->hcon->out && conn->hcon->type == LE_LINK)
+ l2cap_le_conn_ready(conn);
+
read_lock(&l->lock);
for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
@@ -694,7 +707,8 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status)
spin_lock_init(&conn->lock);
rwlock_init(&conn->chan_list.lock);
- setup_timer(&conn->info_timer, l2cap_info_timeout,
+ if (hcon->type != LE_LINK)
+ setup_timer(&conn->info_timer, l2cap_info_timeout,
(unsigned long) conn);
conn->disc_reason = 0x13;
@@ -796,6 +810,37 @@ static void l2cap_sock_destruct(struct sock *sk)
skb_queue_purge(&sk->sk_write_queue);
}
+static inline struct sock *l2cap_get_sock_by_cid(int state, __le16 cid, bdaddr_t *src)
+{
+ struct sock *s;
+ struct sock *sk = NULL, *sk1 = NULL;
+ struct hlist_node *node;
+
+ read_lock(&l2cap_sk_list.lock);
+ sk_for_each(sk, node, &l2cap_sk_list.head) {
+ if (state && sk->sk_state != state)
+ continue;
+
+ if (l2cap_pi(sk)->dcid == cid) {
+ /* Exact match. */
+ if (!bacmp(&bt_sk(sk)->src, src))
+ break;
+
+ /* Closest match */
+ if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY))
+ sk1 = sk;
+ }
+ }
+
+ s = node ? sk : sk1;
+
+ if (s)
+ bh_lock_sock(s);
+ read_unlock(&l2cap_sk_list.lock);
+
+ return s;
+}
+
static void l2cap_sock_cleanup_listen(struct sock *parent)
{
struct sock *sk;
@@ -1008,7 +1053,7 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int alen)
len = min_t(unsigned int, sizeof(la), alen);
memcpy(&la, addr, len);
- if (la.l2_cid)
+ if (la.l2_cid && la.l2_psm)
return -EINVAL;
lock_sock(sk);
@@ -1050,6 +1095,9 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int alen)
l2cap_pi(sk)->sec_level = BT_SECURITY_SDP;
}
+ if (la.l2_cid)
+ l2cap_pi(sk)->dcid = la.l2_cid;
+
write_unlock_bh(&l2cap_sk_list.lock);
done:
@@ -1267,7 +1315,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog)
goto done;
}
- if (!l2cap_pi(sk)->psm) {
+ if (!l2cap_pi(sk)->psm && !l2cap_pi(sk)->dcid) {
bdaddr_t *src = &bt_sk(sk)->src;
u16 psm;
@@ -1377,6 +1425,49 @@ static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr, int *l
return 0;
}
+static void l2cap_le_conn_ready(struct l2cap_conn *conn)
+{
+ struct l2cap_chan_list *list = &conn->chan_list;
+ struct sock *parent, *uninitialized_var(sk);
+
+ BT_DBG("");
+
+ /* Check if we have socket listening on cid */
+ parent = l2cap_get_sock_by_cid(BT_LISTEN, 0x04, conn->src);
+ if (!parent)
+ goto clean;
+
+ /* Check for backlog size */
+ if (sk_acceptq_is_full(parent)) {
+ BT_DBG("backlog full %d", parent->sk_ack_backlog);
+ goto clean;
+ }
+
+ sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP, GFP_ATOMIC);
+ if (!sk)
+ goto clean;
+
+ write_lock_bh(&list->lock);
+
+ hci_conn_hold(conn->hcon);
+
+ l2cap_sock_init(sk, parent);
+ bacpy(&bt_sk(sk)->src, conn->src);
+ bacpy(&bt_sk(sk)->dst, conn->dst);
+
+ __l2cap_chan_add(conn, sk, parent);
+
+ l2cap_sock_set_timer(sk, sk->sk_sndtimeo);
+
+ sk->sk_state = BT_CONNECTED;
+ parent->sk_data_ready(parent, 0);
+
+ write_unlock_bh(&list->lock);
+
+clean:
+ bh_unlock_sock(parent);
+}
+
static int __l2cap_wait_ack(struct sock *sk)
{
DECLARE_WAITQUEUE(wait, current);
--
1.7.1
BLuetooth chips may have separate buffers for
LE traffic. This patch add support to use LE
buffers provided by the chip.
Signed-off-by: Ville Tervo <[email protected]>
---
include/net/bluetooth/hci.h | 1 +
include/net/bluetooth/hci_core.h | 5 +++
net/bluetooth/hci_conn.c | 5 +++
net/bluetooth/hci_core.c | 74 +++++++++++++++++++++++++++++++++++--
net/bluetooth/hci_event.c | 40 +++++++++++++++++++-
5 files changed, 119 insertions(+), 6 deletions(-)
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 02055b9..2103731 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -189,6 +189,7 @@ enum {
#define LMP_EV4 0x01
#define LMP_EV5 0x02
+#define LMP_LE 0x40
#define LMP_SNIFF_SUBR 0x02
#define LMP_EDR_ESCO_2M 0x20
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index 2b7f94a..e2d857a 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -103,15 +103,19 @@ struct hci_dev {
atomic_t cmd_cnt;
unsigned int acl_cnt;
unsigned int sco_cnt;
+ unsigned int le_cnt;
unsigned int acl_mtu;
unsigned int sco_mtu;
+ unsigned int le_mtu;
unsigned int acl_pkts;
unsigned int sco_pkts;
+ unsigned int le_pkts;
unsigned long cmd_last_tx;
unsigned long acl_last_tx;
unsigned long sco_last_tx;
+ unsigned long le_last_tx;
struct workqueue_struct *workqueue;
@@ -473,6 +477,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
#define lmp_sniffsubr_capable(dev) ((dev)->features[5] & LMP_SNIFF_SUBR)
#define lmp_esco_capable(dev) ((dev)->features[3] & LMP_ESCO)
#define lmp_ssp_capable(dev) ((dev)->features[6] & LMP_SIMPLE_PAIR)
+#define lmp_le_capable(dev) ((dev)->features[4] & LMP_LE)
/* ----- HCI protocols ----- */
struct hci_proto {
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 0944c0c..ddc2e5e 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -324,6 +324,11 @@ int hci_conn_del(struct hci_conn *conn)
/* Unacked frames */
hdev->acl_cnt += conn->sent;
+ } else if (conn->type == LE_LINK) {
+ if (hdev->le_pkts)
+ hdev->le_cnt += conn->sent;
+ else
+ hdev->acl_cnt += conn->sent;
} else {
struct hci_conn *acl = conn->link;
if (acl) {
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index bc2a052..45c78c2 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -254,6 +254,14 @@ static void hci_init_req(struct hci_dev *hdev, unsigned long opt)
hci_send_cmd(hdev, HCI_OP_WRITE_CA_TIMEOUT, 2, ¶m);
}
+static void hci_le_init_req(struct hci_dev *hdev, unsigned long opt)
+{
+ BT_DBG("%s", hdev->name);
+
+ /* Read LE buffer size */
+ hci_send_cmd(hdev, HCI_OP_LE_READ_BUFFER_SIZE, 0, NULL);
+}
+
static void hci_scan_req(struct hci_dev *hdev, unsigned long opt)
{
__u8 scan = opt;
@@ -509,6 +517,10 @@ int hci_dev_open(__u16 dev)
ret = __hci_request(hdev, hci_init_req, 0,
msecs_to_jiffies(HCI_INIT_TIMEOUT));
+ if (lmp_le_capable(hdev))
+ ret = __hci_request(hdev, hci_le_init_req, 0,
+ msecs_to_jiffies(HCI_INIT_TIMEOUT));
+
clear_bit(HCI_INIT, &hdev->flags);
}
@@ -645,7 +657,7 @@ int hci_dev_reset(__u16 dev)
hdev->flush(hdev);
atomic_set(&hdev->cmd_cnt, 1);
- hdev->acl_cnt = 0; hdev->sco_cnt = 0;
+ hdev->acl_cnt = 0; hdev->sco_cnt = 0; hdev->le_cnt = 0;
if (!test_bit(HCI_RAW, &hdev->flags))
ret = __hci_request(hdev, hci_reset_req, 0,
@@ -1456,8 +1468,25 @@ static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int
}
if (conn) {
- int cnt = (type == ACL_LINK ? hdev->acl_cnt : hdev->sco_cnt);
- int q = cnt / num;
+ int cnt, q;
+
+ switch (conn->type) {
+ case ACL_LINK:
+ cnt = hdev->acl_cnt;
+ break;
+ case SCO_LINK:
+ case ESCO_LINK:
+ cnt = hdev->sco_cnt;
+ break;
+ case LE_LINK:
+ cnt = hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
+ break;
+ default:
+ cnt = 0;
+ BT_ERR("Unknown link type");
+ }
+
+ q = cnt / num;
*quote = q ? q : 1;
} else
*quote = 0;
@@ -1556,6 +1585,40 @@ static inline void hci_sched_esco(struct hci_dev *hdev)
}
}
+static inline void hci_sched_le(struct hci_dev *hdev)
+{
+ struct hci_conn *conn;
+ struct sk_buff *skb;
+ int quote, cnt;
+
+ BT_DBG("%s", hdev->name);
+
+ if (!test_bit(HCI_RAW, &hdev->flags)) {
+ /* ACL tx timeout must be longer than maximum
+ * link supervision timeout (40.9 seconds) */
+ if (!hdev->le_cnt &&
+ time_after(jiffies, hdev->le_last_tx + HZ * 45))
+ hci_acl_tx_to(hdev);
+ }
+
+ cnt = hdev->le_pkts ? hdev->le_cnt : hdev->acl_cnt;
+ while (cnt && (conn = hci_low_sent(hdev, LE_LINK, "e))) {
+ while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+ BT_DBG("skb %p len %d", skb, skb->len);
+
+ hci_send_frame(skb);
+ hdev->le_last_tx = jiffies;
+
+ cnt--;
+ conn->sent++;
+ }
+ }
+ if (hdev->le_pkts)
+ hdev->le_cnt = cnt;
+ else
+ hdev->acl_cnt = cnt;
+}
+
static void hci_tx_task(unsigned long arg)
{
struct hci_dev *hdev = (struct hci_dev *) arg;
@@ -1563,7 +1626,8 @@ static void hci_tx_task(unsigned long arg)
read_lock(&hci_task_lock);
- BT_DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt);
+ BT_DBG("%s acl %d sco %d le %d", hdev->name, hdev->acl_cnt,
+ hdev->sco_cnt, hdev->le_cnt);
/* Schedule queues and send stuff to HCI driver */
@@ -1573,6 +1637,8 @@ static void hci_tx_task(unsigned long arg)
hci_sched_esco(hdev);
+ hci_sched_le(hdev);
+
/* Send next queued raw (unknown type) packet */
while ((skb = skb_dequeue(&hdev->raw_q)))
hci_send_frame(skb);
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 92c8621..da23502 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -539,6 +539,26 @@ static void hci_cc_read_bd_addr(struct hci_dev *hdev, struct sk_buff *skb)
hci_req_complete(hdev, rp->status);
}
+static void hci_cc_le_read_buffer_size(struct hci_dev *hdev,
+ struct sk_buff *skb)
+{
+ struct hci_rp_le_read_buffer_size *rp = (void *) skb->data;
+
+ BT_DBG("%s status 0x%x", hdev->name, rp->status);
+
+ if (rp->status)
+ return;
+
+ hdev->le_mtu = __le16_to_cpu(rp->le_mtu);
+ hdev->le_pkts = rp->le_max_pkt;
+
+ hdev->le_cnt = hdev->le_pkts;
+
+ BT_DBG("%s le mtu %d:%d", hdev->name, hdev->le_mtu, hdev->le_pkts);
+
+ hci_req_complete(hdev, rp->status);
+}
+
static inline void hci_cs_inquiry(struct hci_dev *hdev, __u8 status)
{
BT_DBG("%s status 0x%x", hdev->name, status);
@@ -1353,6 +1373,10 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk
hci_cc_read_bd_addr(hdev, skb);
break;
+ case HCI_OP_LE_READ_BUFFER_SIZE:
+ hci_cc_le_read_buffer_size(hdev, skb);
+ break;
+
default:
BT_DBG("%s opcode 0x%x", hdev->name, opcode);
break;
@@ -1490,10 +1514,22 @@ static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *s
conn->sent -= count;
if (conn->type == ACL_LINK) {
- if ((hdev->acl_cnt += count) > hdev->acl_pkts)
+ hdev->acl_cnt += count;
+ if (hdev->acl_cnt > hdev->acl_pkts)
hdev->acl_cnt = hdev->acl_pkts;
+ } else if (conn->type == LE_LINK) {
+ if (hdev->le_pkts) {
+ hdev->le_cnt += count;
+ if (hdev->le_cnt > hdev->le_pkts)
+ hdev->le_cnt = hdev->le_pkts;
+ } else {
+ hdev->acl_cnt += count;
+ if (hdev->acl_cnt > hdev->acl_pkts)
+ hdev->acl_cnt = hdev->acl_pkts;
+ }
} else {
- if ((hdev->sco_cnt += count) > hdev->sco_pkts)
+ hdev->sco_cnt += count;
+ if (hdev->sco_cnt > hdev->sco_pkts)
hdev->sco_cnt = hdev->sco_pkts;
}
}
--
1.7.1
Add needed HCI command and event structs to
create LE connections.
Signed-off-by: Ville Tervo <[email protected]>
---
include/net/bluetooth/hci.h | 49 +++++++++++++++++++++++++++++++++++++++++++
1 files changed, 49 insertions(+), 0 deletions(-)
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index e30e008..ee5beec 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -593,6 +593,36 @@ struct hci_rp_read_bd_addr {
bdaddr_t bdaddr;
} __packed;
+#define HCI_OP_LE_SET_EVENT_MASK 0x2001
+struct hci_cp_le_set_event_mask {
+ __u8 mask[8];
+} __packed;
+
+#define HCI_OP_LE_READ_BUFFER_SIZE 0x2002
+struct hci_rp_le_read_buffer_size {
+ __u8 status;
+ __le16 le_mtu;
+ __u8 le_max_pkt;
+} __packed;
+
+#define HCI_OP_LE_CREATE_CONN 0x200d
+struct hci_cp_le_create_conn {
+ __le16 scan_interval;
+ __le16 scan_window;
+ __u8 filter_policy;
+ __u8 peer_addr_type;
+ bdaddr_t peer_addr;
+ __u8 own_address_type;
+ __le16 conn_interval_min;
+ __le16 conn_interval_max;
+ __le16 conn_latency;
+ __le16 supervision_timeout;
+ __le16 min_ce_len;
+ __le16 max_ce_len;
+} __packed;
+
+#define HCI_OP_LE_CREATE_CONN_CANCEL 0x200e
+
/* ---- HCI Events ---- */
#define HCI_EV_INQUIRY_COMPLETE 0x01
@@ -845,6 +875,25 @@ struct hci_ev_remote_host_features {
__u8 features[8];
} __packed;
+#define HCI_EV_LE_META 0x3e
+struct hci_ev_le_meta {
+ __u8 subevent;
+} __packed;
+
+/* Low energy meta events */
+#define HCI_EV_LE_CONN_COMPLETE 0x01
+struct hci_ev_le_conn_complete {
+ __u8 status;
+ __le16 handle;
+ __u8 role;
+ __u8 bdaddr_type;
+ bdaddr_t bdaddr;
+ __le16 interval;
+ __le16 latency;
+ __le16 supervision_timeout;
+ __u8 clk_accurancy;
+} __packed;
+
/* Internal events generated by Bluetooth stack */
#define HCI_EV_STACK_INTERNAL 0xfd
struct hci_ev_stack_internal {
--
1.7.1
l2cap over LE links can be disconnected without sending
disconnect command first.
Signed-off-by: Ville Tervo <[email protected]>
---
net/bluetooth/l2cap.c | 15 ++++++++++-----
1 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
index 3913ba5..2da613b 100644
--- a/net/bluetooth/l2cap.c
+++ b/net/bluetooth/l2cap.c
@@ -873,6 +873,8 @@ static void l2cap_sock_kill(struct sock *sk)
static void __l2cap_sock_close(struct sock *sk, int reason)
{
+ struct l2cap_conn *conn;
+
BT_DBG("sk %p state %d socket %p", sk, sk->sk_state, sk->sk_socket);
switch (sk->sk_state) {
@@ -882,8 +884,10 @@ static void __l2cap_sock_close(struct sock *sk, int reason)
case BT_CONNECTED:
case BT_CONFIG:
- if (sk->sk_type == SOCK_SEQPACKET ||
- sk->sk_type == SOCK_STREAM) {
+ conn = l2cap_pi(sk)->conn;
+ if ((sk->sk_type == SOCK_SEQPACKET ||
+ sk->sk_type == SOCK_STREAM) &&
+ conn->hcon->type != LE_LINK) {
struct l2cap_conn *conn = l2cap_pi(sk)->conn;
l2cap_sock_set_timer(sk, sk->sk_sndtimeo);
@@ -893,9 +897,10 @@ static void __l2cap_sock_close(struct sock *sk, int reason)
break;
case BT_CONNECT2:
- if (sk->sk_type == SOCK_SEQPACKET ||
- sk->sk_type == SOCK_STREAM) {
- struct l2cap_conn *conn = l2cap_pi(sk)->conn;
+ conn = l2cap_pi(sk)->conn;
+ if ((sk->sk_type == SOCK_SEQPACKET ||
+ sk->sk_type == SOCK_STREAM) &&
+ conn->hcon->type != LE_LINK) {
struct l2cap_conn_rsp rsp;
__u16 result;
--
1.7.1
Bluetooth V4.0 adds support for Low Energy (LE)
connections. Specification introduses new set
of hci commands to control LE connection.
This patch adds logic to create, cancel and
disconnect LE connections
Signed-off-by: Ville Tervo <[email protected]>
---
include/net/bluetooth/hci.h | 2 +
include/net/bluetooth/hci_core.h | 25 +++++++++--
net/bluetooth/hci_conn.c | 51 +++++++++++++++++++-
net/bluetooth/hci_event.c | 93 ++++++++++++++++++++++++++++++++++++++
4 files changed, 164 insertions(+), 7 deletions(-)
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index ee5beec..02055b9 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -159,6 +159,8 @@ enum {
#define SCO_LINK 0x00
#define ACL_LINK 0x01
#define ESCO_LINK 0x02
+/* Low Energy links do not have defined link type. Use invented one */
+#define LE_LINK 0x80
/* LMP features */
#define LMP_3SLOT 0x01
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index ebec8c9..2b7f94a 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -60,6 +60,7 @@ struct hci_conn_hash {
spinlock_t lock;
unsigned int acl_num;
unsigned int sco_num;
+ unsigned int le_num;
};
struct bdaddr_list {
@@ -272,20 +273,36 @@ static inline void hci_conn_hash_add(struct hci_dev *hdev, struct hci_conn *c)
{
struct hci_conn_hash *h = &hdev->conn_hash;
list_add(&c->list, &h->list);
- if (c->type == ACL_LINK)
+ switch (c->type) {
+ case ACL_LINK:
h->acl_num++;
- else
+ break;
+ case LE_LINK:
+ h->le_num++;
+ break;
+ case SCO_LINK:
+ case ESCO_LINK:
h->sco_num++;
+ break;
+ }
}
static inline void hci_conn_hash_del(struct hci_dev *hdev, struct hci_conn *c)
{
struct hci_conn_hash *h = &hdev->conn_hash;
list_del(&c->list);
- if (c->type == ACL_LINK)
+ switch (c->type) {
+ case ACL_LINK:
h->acl_num--;
- else
+ break;
+ case LE_LINK:
+ h->le_num--;
+ break;
+ case SCO_LINK:
+ case ESCO_LINK:
h->sco_num--;
+ break;
+ }
}
static inline struct hci_conn *hci_conn_hash_lookup_handle(struct hci_dev *hdev,
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 0b1e460..0944c0c 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -45,6 +45,32 @@
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
+void hci_le_connect(struct hci_conn *conn)
+{
+ struct hci_dev *hdev = conn->hdev;
+ struct hci_cp_le_create_conn cp;
+
+ conn->state = BT_CONNECT;
+ conn->out = 1;
+
+ 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.conn_interval_min = cpu_to_le16(0x0008);
+ cp.conn_interval_max = cpu_to_le16(0x0100);
+ cp.supervision_timeout = cpu_to_le16(0x0064);
+ cp.min_ce_len = cpu_to_le16(0x0001);
+ cp.max_ce_len = cpu_to_le16(0x0001);
+
+ hci_send_cmd(hdev, HCI_OP_LE_CREATE_CONN, sizeof(cp), &cp);
+}
+
+static void hci_le_connect_cancel(struct hci_conn *conn)
+{
+ hci_send_cmd(conn->hdev, HCI_OP_LE_CREATE_CONN_CANCEL, 0, NULL);
+}
+
void hci_acl_connect(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
@@ -192,8 +218,12 @@ static void hci_conn_timeout(unsigned long arg)
switch (conn->state) {
case BT_CONNECT:
case BT_CONNECT2:
- if (conn->type == ACL_LINK && conn->out)
- hci_acl_connect_cancel(conn);
+ if (conn->out) {
+ if (conn->type == ACL_LINK)
+ hci_acl_connect_cancel(conn);
+ else if (conn->type == LE_LINK)
+ hci_le_connect_cancel(conn);
+ }
break;
case BT_CONFIG:
case BT_CONNECTED:
@@ -359,15 +389,30 @@ struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src)
}
EXPORT_SYMBOL(hci_get_route);
-/* Create SCO or ACL connection.
+/* Create SCO, ACL or LE connection.
* Device _must_ be locked */
struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8 sec_level, __u8 auth_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) {
+ le = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst);
+ if (!le)
+ le = hci_conn_add(hdev, LE_LINK, dst);
+ if (!le)
+ return NULL;
+ if (le->state == BT_OPEN)
+ hci_le_connect(le);
+
+ hci_conn_hold(le);
+
+ return le;
+ }
+
if (!(acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst))) {
if (!(acl = hci_conn_add(hdev, ACL_LINK, dst)))
return NULL;
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 84093b0..92c8621 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -822,6 +822,43 @@ static void hci_cs_exit_sniff_mode(struct hci_dev *hdev, __u8 status)
hci_dev_unlock(hdev);
}
+static void hci_cs_le_create_conn(struct hci_dev *hdev, __u8 status)
+{
+ struct hci_cp_le_create_conn *cp;
+ struct hci_conn *conn;
+
+ BT_DBG("%s status 0x%x", hdev->name, status);
+
+ cp = hci_sent_cmd_data(hdev, HCI_OP_LE_CREATE_CONN);
+ if (!cp)
+ return;
+
+ hci_dev_lock(hdev);
+
+ conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->peer_addr);
+
+ BT_DBG("%s bdaddr %s conn %p", hdev->name, batostr(&cp->peer_addr),
+ conn);
+
+ if (status) {
+ if (conn && conn->state == BT_CONNECT) {
+ conn->state = BT_CLOSED;
+ hci_proto_connect_cfm(conn, status);
+ hci_conn_del(conn);
+ }
+ } else {
+ if (!conn) {
+ conn = hci_conn_add(hdev, LE_LINK, &cp->peer_addr);
+ if (conn)
+ conn->out = 1;
+ else
+ BT_ERR("No memory for new connection");
+ }
+ }
+
+ hci_dev_unlock(hdev);
+}
+
static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
{
__u8 status = *((__u8 *) skb->data);
@@ -1382,6 +1419,10 @@ static inline void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb)
hci_cs_exit_sniff_mode(hdev, ev->status);
break;
+ case HCI_OP_LE_CREATE_CONN:
+ hci_cs_le_create_conn(hdev, ev->status);
+ break;
+
default:
BT_DBG("%s opcode 0x%x", hdev->name, opcode);
break;
@@ -1827,6 +1868,54 @@ static inline void hci_remote_host_features_evt(struct hci_dev *hdev, struct sk_
hci_dev_unlock(hdev);
}
+static inline void hci_le_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct hci_ev_le_conn_complete *ev = (void *) skb->data;
+ struct hci_conn *conn;
+
+ BT_DBG("%s status %d", hdev->name, ev->status);
+
+ hci_dev_lock(hdev);
+
+ conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &ev->bdaddr);
+ if (!conn)
+ goto unlock;
+
+ if (ev->status) {
+ hci_proto_connect_cfm(conn, ev->status);
+ conn->state = BT_CLOSED;
+ hci_conn_del(conn);
+ goto unlock;
+ }
+
+ conn->handle = __le16_to_cpu(ev->handle);
+ conn->state = BT_CONNECTED;
+
+ hci_conn_hold_device(conn);
+ hci_conn_add_sysfs(conn);
+
+ hci_proto_connect_cfm(conn, ev->status);
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
+static inline void hci_le_meta_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct hci_ev_le_meta *le_ev = (void *) skb->data;
+
+ skb_pull(skb, sizeof(*le_ev));
+
+ switch (le_ev->subevent) {
+ case HCI_EV_LE_CONN_COMPLETE:
+ hci_le_conn_complete_evt(hdev, skb);
+ break;
+
+ default:
+ break;
+ }
+}
+
void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb)
{
struct hci_event_hdr *hdr = (void *) skb->data;
@@ -1963,6 +2052,10 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb)
hci_remote_host_features_evt(hdev, skb);
break;
+ case HCI_EV_LE_META:
+ hci_le_meta_evt(hdev, skb);
+ break;
+
default:
BT_DBG("%s event 0x%x", hdev->name, event);
break;
--
1.7.1
Add basic LE connection support to L2CAP. LE
connection can be created by specifying cid
in struct sockaddr_l2
Signed-off-by: Ville Tervo <[email protected]>
---
include/net/bluetooth/l2cap.h | 3 +++
net/bluetooth/l2cap.c | 32 ++++++++++++++++++++++++--------
2 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index c819c8b..cc3a140 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -160,6 +160,9 @@ struct l2cap_conn_rsp {
/* channel indentifier */
#define L2CAP_CID_SIGNALING 0x0001
#define L2CAP_CID_CONN_LESS 0x0002
+#define L2CAP_CID_LE_DATA 0x0004
+#define L2CAP_CID_LE_SIGNALING 0x0005
+#define L2CAP_CID_SMP 0x0006
#define L2CAP_CID_DYN_START 0x0040
#define L2CAP_CID_DYN_END 0xffff
diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
index 6f931cc..18643af 100644
--- a/net/bluetooth/l2cap.c
+++ b/net/bluetooth/l2cap.c
@@ -617,6 +617,12 @@ static void l2cap_conn_ready(struct l2cap_conn *conn)
for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
bh_lock_sock(sk);
+ if (conn->hcon->type == LE_LINK) {
+ l2cap_sock_clear_timer(sk);
+ sk->sk_state = BT_CONNECTED;
+ sk->sk_state_change(sk);
+ }
+
if (sk->sk_type != SOCK_SEQPACKET &&
sk->sk_type != SOCK_STREAM) {
l2cap_sock_clear_timer(sk);
@@ -675,7 +681,11 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status)
BT_DBG("hcon %p conn %p", hcon, conn);
- conn->mtu = hcon->hdev->acl_mtu;
+ if (hcon->hdev->le_mtu && hcon->type == LE_LINK)
+ conn->mtu = hcon->hdev->le_mtu;
+ else
+ conn->mtu = hcon->hdev->acl_mtu;
+
conn->src = &hcon->hdev->bdaddr;
conn->dst = &hcon->dst;
@@ -1102,8 +1112,13 @@ static int l2cap_do_connect(struct sock *sk)
}
}
- hcon = hci_connect(hdev, ACL_LINK, dst,
+ if (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA)
+ hcon = hci_connect(hdev, LE_LINK, dst,
+ l2cap_pi(sk)->sec_level, auth_type);
+ else
+ hcon = hci_connect(hdev, ACL_LINK, dst,
l2cap_pi(sk)->sec_level, auth_type);
+
if (!hcon)
goto done;
@@ -1154,13 +1169,13 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int al
len = min_t(unsigned int, sizeof(la), alen);
memcpy(&la, addr, len);
- if (la.l2_cid)
+ if (la.l2_cid && la.l2_psm)
return -EINVAL;
lock_sock(sk);
if ((sk->sk_type == SOCK_SEQPACKET || sk->sk_type == SOCK_STREAM)
- && !la.l2_psm) {
+ && !(la.l2_psm || la.l2_cid)) {
err = -EINVAL;
goto done;
}
@@ -1202,14 +1217,15 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int al
/* PSM must be odd and lsb of upper byte must be 0 */
if ((__le16_to_cpu(la.l2_psm) & 0x0101) != 0x0001 &&
- sk->sk_type != SOCK_RAW) {
+ sk->sk_type != SOCK_RAW && !la.l2_cid) {
err = -EINVAL;
goto done;
}
- /* Set destination address and psm */
+ /* Set destination address and psm or cid */
bacpy(&bt_sk(sk)->dst, &la.l2_bdaddr);
l2cap_pi(sk)->psm = la.l2_psm;
+ l2cap_pi(sk)->dcid = la.l2_cid;
err = l2cap_do_connect(sk);
if (err)
@@ -4525,7 +4541,7 @@ static int l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status);
- if (hcon->type != ACL_LINK)
+ if (!(hcon->type == ACL_LINK || hcon->type == LE_LINK))
return -EINVAL;
if (!status) {
@@ -4554,7 +4570,7 @@ static int l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
{
BT_DBG("hcon %p reason %d", hcon, reason);
- if (hcon->type != ACL_LINK)
+ if (!(hcon->type == ACL_LINK || hcon->type == LE_LINK))
return -EINVAL;
l2cap_conn_del(hcon, bt_err(reason));
--
1.7.1
On Fri, Oct 22, 2010 at 09:14:57PM +0200, ext Gustavo F. Padovan wrote:
> Hi Ville,
>
> * Ville Tervo <[email protected]> [2010-10-18 16:02:54 +0300]:
>
> > Add basic LE connection support to L2CAP. LE
> > connection can be created by specifying cid
> > in struct sockaddr_l2
> >
> > Signed-off-by: Ville Tervo <[email protected]>
> > ---
> > include/net/bluetooth/l2cap.h | 3 +++
> > net/bluetooth/l2cap.c | 32 ++++++++++++++++++++++++--------
> > 2 files changed, 27 insertions(+), 8 deletions(-)
> >
> > diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> > index c819c8b..cc3a140 100644
> > --- a/include/net/bluetooth/l2cap.h
> > +++ b/include/net/bluetooth/l2cap.h
> > @@ -160,6 +160,9 @@ struct l2cap_conn_rsp {
> > /* channel indentifier */
> > #define L2CAP_CID_SIGNALING 0x0001
> > #define L2CAP_CID_CONN_LESS 0x0002
> > +#define L2CAP_CID_LE_DATA 0x0004
> > +#define L2CAP_CID_LE_SIGNALING 0x0005
> > +#define L2CAP_CID_SMP 0x0006
> > #define L2CAP_CID_DYN_START 0x0040
> > #define L2CAP_CID_DYN_END 0xffff
> >
> > diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
> > index 16049de..bf5daf3 100644
> > --- a/net/bluetooth/l2cap.c
> > +++ b/net/bluetooth/l2cap.c
> > @@ -617,6 +617,12 @@ static void l2cap_conn_ready(struct l2cap_conn *conn)
> > for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
> > bh_lock_sock(sk);
> >
> > + if (conn->hcon->type == LE_LINK) {
> > + l2cap_sock_clear_timer(sk);
> > + sk->sk_state = BT_CONNECTED;
> > + sk->sk_state_change(sk);
> > + }
> > +
> > if (sk->sk_type != SOCK_SEQPACKET &&
> > sk->sk_type != SOCK_STREAM) {
> > l2cap_sock_clear_timer(sk);
> > @@ -675,7 +681,11 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status)
> >
> > BT_DBG("hcon %p conn %p", hcon, conn);
> >
> > - conn->mtu = hcon->hdev->acl_mtu;
> > + if (hcon->hdev->le_mtu && hcon->type == LE_LINK)
> > + conn->mtu = hcon->hdev->le_mtu;
> > + else
> > + conn->mtu = hcon->hdev->acl_mtu;
> > +
> > conn->src = &hcon->hdev->bdaddr;
> > conn->dst = &hcon->dst;
> >
> > @@ -1102,8 +1112,13 @@ static int l2cap_do_connect(struct sock *sk)
> > }
> > }
> >
> > - hcon = hci_connect(hdev, ACL_LINK, dst,
> > + if (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA)
> > + hcon = hci_connect(hdev, LE_LINK, dst,
> > + l2cap_pi(sk)->sec_level, auth_type);
> > + else
>
>
> I think that "else if (l2cap_pi(sk)->psm)" is better here, we do not
> want to permit go ahead with psm 0.
That is checked already in l2cap_sock_connect().
--
Ville
On Fri, Oct 22, 2010 at 08:53:58PM +0200, ext Gustavo F. Padovan wrote:
> Hi Ville,
>
> * Ville Tervo <[email protected]> [2010-10-18 16:02:53 +0300]:
>
> > BLuetooth chips may have separate buffers for
> > LE traffic. This patch add support to use LE
> > buffers provided by the chip.
> >
> > Signed-off-by: Ville Tervo <[email protected]>
> > ---
> > include/net/bluetooth/hci.h | 2 +
> > include/net/bluetooth/hci_core.h | 5 +++
> > net/bluetooth/hci_conn.c | 6 +++
> > net/bluetooth/hci_core.c | 74 +++++++++++++++++++++++++++++++++++--
> > net/bluetooth/hci_event.c | 40 +++++++++++++++++++-
> > 5 files changed, 121 insertions(+), 6 deletions(-)
> >
> > diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
> > index 02055b9..b42edf0 100644
> > --- a/include/net/bluetooth/hci.h
> > +++ b/include/net/bluetooth/hci.h
> > @@ -189,6 +189,8 @@ enum {
> >
> > #define LMP_EV4 0x01
> > #define LMP_EV5 0x02
> > +#define LMP_NO_BREDR 0x20
>
> You are not using this one.
I'll remove it.
>
> > +#define LMP_LE 0x40
> >
> > #define LMP_SNIFF_SUBR 0x02
> > #define LMP_EDR_ESCO_2M 0x20
> > diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> > index fc2aaee..326d290 100644
> > --- a/include/net/bluetooth/hci_core.h
> > +++ b/include/net/bluetooth/hci_core.h
> > @@ -103,15 +103,19 @@ struct hci_dev {
> > atomic_t cmd_cnt;
> > unsigned int acl_cnt;
> > unsigned int sco_cnt;
> > + unsigned int le_cnt;
> >
> > unsigned int acl_mtu;
> > unsigned int sco_mtu;
> > + unsigned int le_mtu;
> > unsigned int acl_pkts;
> > unsigned int sco_pkts;
> > + unsigned int le_pkts;
> >
> > unsigned long cmd_last_tx;
> > unsigned long acl_last_tx;
> > unsigned long sco_last_tx;
> > + unsigned long le_last_tx;
> >
> > struct workqueue_struct *workqueue;
> >
> > @@ -469,6 +473,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
> > #define lmp_sniffsubr_capable(dev) ((dev)->features[5] & LMP_SNIFF_SUBR)
> > #define lmp_esco_capable(dev) ((dev)->features[3] & LMP_ESCO)
> > #define lmp_ssp_capable(dev) ((dev)->features[6] & LMP_SIMPLE_PAIR)
> > +#define lmp_le_capable(dev) ((dev)->features[4] & LMP_LE)
> >
> > /* ----- HCI protocols ----- */
> > struct hci_proto {
> > diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> > index c1eb8e0..c7309e4 100644
> > --- a/net/bluetooth/hci_conn.c
> > +++ b/net/bluetooth/hci_conn.c
> > @@ -324,6 +324,11 @@ int hci_conn_del(struct hci_conn *conn)
> >
> > /* Unacked frames */
> > hdev->acl_cnt += conn->sent;
> > + } else if (conn->type == LE_LINK) {
> > + if (hdev->le_pkts)
> > + hdev->le_cnt += conn->sent;
> > + else
> > + hdev->acl_cnt += conn->sent;
> > } else {
> > struct hci_conn *acl = conn->link;
> > if (acl) {
> > @@ -409,6 +414,7 @@ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8
> > return NULL;
> >
> > hci_le_connect(le);
> > + hci_conn_hold(le);
> >
>
> This should be in 2/6, right?
Yes.
--
Ville
On Fri, Oct 22, 2010 at 08:46:37PM +0200, ext Gustavo F. Padovan wrote:
> * Ville Tervo <[email protected]> [2010-10-18 16:02:52 +0300]:
>
> > Bluetooth V4.0 adds support for Low Energy (LE)
> > connections. Specification introduses new set
> > of hci commands to control LE connection.
> > This patch adds logic to create, cancel and
> > disconnect LE connections
> >
> > Signed-off-by: Ville Tervo <[email protected]>
> > ---
> > include/net/bluetooth/hci.h | 2 +
> > include/net/bluetooth/hci_core.h | 21 +++++++--
> > net/bluetooth/hci_conn.c | 51 +++++++++++++++++++-
> > net/bluetooth/hci_event.c | 94 +++++++++++++++++++++++++++++++++++++-
> > 4 files changed, 160 insertions(+), 8 deletions(-)
> >
> > diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
> > index ee5beec..02055b9 100644
> > --- a/include/net/bluetooth/hci.h
> > +++ b/include/net/bluetooth/hci.h
> > @@ -159,6 +159,8 @@ enum {
> > #define SCO_LINK 0x00
> > #define ACL_LINK 0x01
> > #define ESCO_LINK 0x02
> > +/* Low Energy links do not have defined link type. Use invented one */
> > +#define LE_LINK 0x80
> >
> > /* LMP features */
> > #define LMP_3SLOT 0x01
> > diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> > index ebec8c9..fc2aaee 100644
> > --- a/include/net/bluetooth/hci_core.h
> > +++ b/include/net/bluetooth/hci_core.h
> > @@ -60,6 +60,7 @@ struct hci_conn_hash {
> > spinlock_t lock;
> > unsigned int acl_num;
> > unsigned int sco_num;
> > + unsigned int le_num;
> > };
> >
> > struct bdaddr_list {
> > @@ -272,20 +273,32 @@ static inline void hci_conn_hash_add(struct hci_dev *hdev, struct hci_conn *c)
> > {
> > struct hci_conn_hash *h = &hdev->conn_hash;
> > list_add(&c->list, &h->list);
> > - if (c->type == ACL_LINK)
> > + switch (c->type) {
> > + case ACL_LINK:
> > h->acl_num++;
> > - else
> > + break;
> > + case LE_LINK:
> > + h->le_num++;
> > + break;
> > + default:
>
> I would add a 'case SCO_LINK' here. It changes nothing actually, but
> make switch easy to understand.
Also ESCO_LINK needs to be added in that case. I'll add those both.
>
> > h->sco_num++;
> > + }
> > }
> >
> > static inline void hci_conn_hash_del(struct hci_dev *hdev, struct hci_conn *c)
> > {
> > struct hci_conn_hash *h = &hdev->conn_hash;
> > list_del(&c->list);
> > - if (c->type == ACL_LINK)
> > + switch (c->type) {
> > + case ACL_LINK:
> > h->acl_num--;
> > - else
> > + break;
> > + case LE_LINK:
> > + h->le_num--;
> > + break;
> > + default:
>
> Same here.
>
> > h->sco_num--;
> > + }
> > }
> >
> > static inline struct hci_conn *hci_conn_hash_lookup_handle(struct hci_dev *hdev,
> > diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> > index 0b1e460..c1eb8e0 100644
> > --- a/net/bluetooth/hci_conn.c
> > +++ b/net/bluetooth/hci_conn.c
> > @@ -45,6 +45,32 @@
> > #include <net/bluetooth/bluetooth.h>
> > #include <net/bluetooth/hci_core.h>
> >
> > +void hci_le_connect(struct hci_conn *conn)
> > +{
> > + struct hci_dev *hdev = conn->hdev;
> > + struct hci_cp_le_create_conn cp;
> > +
> > + conn->state = BT_CONNECT;
> > + conn->out = 1;
> > +
> > + 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.conn_interval_min = cpu_to_le16(0x0008);
> > + cp.conn_interval_max = cpu_to_le16(0x0100);
> > + cp.supervision_timeout = cpu_to_le16(0x0064);
> > + cp.min_ce_len = cpu_to_le16(0x0001);
> > + cp.max_ce_len = cpu_to_le16(0x0001);
> > +
> > + hci_send_cmd(hdev, HCI_OP_LE_CREATE_CONN, sizeof(cp), &cp);
> > +}
> > +
> > +static void hci_le_connect_cancel(struct hci_conn *conn)
> > +{
> > + hci_send_cmd(conn->hdev, HCI_OP_LE_CREATE_CONN_CANCEL, 0, NULL);
> > +}
> > +
> > void hci_acl_connect(struct hci_conn *conn)
> > {
> > struct hci_dev *hdev = conn->hdev;
> > @@ -192,8 +218,12 @@ static void hci_conn_timeout(unsigned long arg)
> > switch (conn->state) {
> > case BT_CONNECT:
> > case BT_CONNECT2:
> > - if (conn->type == ACL_LINK && conn->out)
> > - hci_acl_connect_cancel(conn);
> > + if (conn->out) {
> > + if (conn->type == ACL_LINK)
> > + hci_acl_connect_cancel(conn);
> > + else if (conn->type == LE_LINK)
> > + hci_le_connect_cancel(conn);
> > + }
> > break;
> > case BT_CONFIG:
> > case BT_CONNECTED:
> > @@ -359,15 +389,30 @@ struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src)
> > }
> > EXPORT_SYMBOL(hci_get_route);
> >
> > -/* Create SCO or ACL connection.
> > +/* Create SCO, ACL or LE connection.
> > * Device _must_ be locked */
> > struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8 sec_level, __u8 auth_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) {
> > + le = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst);
> > +
> > + if (!le)
> > + le = hci_conn_add(hdev, LE_LINK, dst);
> > +
> > + if (!le)
> > + return NULL;
> > +
> > + hci_le_connect(le);
> > +
> > + return le;
> > + }
> > +
> > if (!(acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst))) {
> > if (!(acl = hci_conn_add(hdev, ACL_LINK, dst)))
> > return NULL;
> > diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
> > index 84093b0..4061613 100644
> > --- a/net/bluetooth/hci_event.c
> > +++ b/net/bluetooth/hci_event.c
> > @@ -822,6 +822,43 @@ static void hci_cs_exit_sniff_mode(struct hci_dev *hdev, __u8 status)
> > hci_dev_unlock(hdev);
> > }
> >
> > +static void hci_cs_le_create_conn(struct hci_dev *hdev, __u8 status)
> > +{
> > + struct hci_cp_le_create_conn *cp;
> > + struct hci_conn *conn;
> > +
> > + BT_DBG("%s status 0x%x", hdev->name, status);
> > +
> > + cp = hci_sent_cmd_data(hdev, HCI_OP_LE_CREATE_CONN);
> > + if (!cp)
> > + return;
> > +
> > + hci_dev_lock(hdev);
> > +
> > + conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->peer_addr);
> > +
> > + BT_DBG("%s bdaddr %s conn %p", hdev->name, batostr(&cp->peer_addr),
> > + conn);
> > +
> > + if (status) {
> > + if (conn && conn->state == BT_CONNECT) {
> > + conn->state = BT_CLOSED;
> > + hci_proto_connect_cfm(conn, status);
> > + hci_conn_del(conn);
> > + }
> > + } else {
> > + if (!conn) {
> > + conn = hci_conn_add(hdev, LE_LINK, &cp->peer_addr);
> > + if (conAvoid things like that in your patch.n)
> > + conn->out = 1;
> > + else
> > + BT_ERR("No memory for new connection");
> > + }
> > + }
> > +
> > + hci_dev_unlock(hdev);
> > +}
> > +
> > static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
> > {
> > __u8 status = *((__u8 *) skb->data);
> > @@ -1024,7 +1061,6 @@ static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff
> > conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle));
> > if (conn) {
> > conn->state = BT_CLOSED;
> > -
>
> Avoid things like that in your patch.
My mistake. Will remove it.
--
Ville
Hi Ville,
* Ville Tervo <[email protected]> [2010-10-18 16:02:54 +0300]:
> Add basic LE connection support to L2CAP. LE
> connection can be created by specifying cid
> in struct sockaddr_l2
>
> Signed-off-by: Ville Tervo <[email protected]>
> ---
> include/net/bluetooth/l2cap.h | 3 +++
> net/bluetooth/l2cap.c | 32 ++++++++++++++++++++++++--------
> 2 files changed, 27 insertions(+), 8 deletions(-)
>
> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> index c819c8b..cc3a140 100644
> --- a/include/net/bluetooth/l2cap.h
> +++ b/include/net/bluetooth/l2cap.h
> @@ -160,6 +160,9 @@ struct l2cap_conn_rsp {
> /* channel indentifier */
> #define L2CAP_CID_SIGNALING 0x0001
> #define L2CAP_CID_CONN_LESS 0x0002
> +#define L2CAP_CID_LE_DATA 0x0004
> +#define L2CAP_CID_LE_SIGNALING 0x0005
> +#define L2CAP_CID_SMP 0x0006
> #define L2CAP_CID_DYN_START 0x0040
> #define L2CAP_CID_DYN_END 0xffff
>
> diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
> index 16049de..bf5daf3 100644
> --- a/net/bluetooth/l2cap.c
> +++ b/net/bluetooth/l2cap.c
> @@ -617,6 +617,12 @@ static void l2cap_conn_ready(struct l2cap_conn *conn)
> for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
> bh_lock_sock(sk);
>
> + if (conn->hcon->type == LE_LINK) {
> + l2cap_sock_clear_timer(sk);
> + sk->sk_state = BT_CONNECTED;
> + sk->sk_state_change(sk);
> + }
> +
> if (sk->sk_type != SOCK_SEQPACKET &&
> sk->sk_type != SOCK_STREAM) {
> l2cap_sock_clear_timer(sk);
> @@ -675,7 +681,11 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status)
>
> BT_DBG("hcon %p conn %p", hcon, conn);
>
> - conn->mtu = hcon->hdev->acl_mtu;
> + if (hcon->hdev->le_mtu && hcon->type == LE_LINK)
> + conn->mtu = hcon->hdev->le_mtu;
> + else
> + conn->mtu = hcon->hdev->acl_mtu;
> +
> conn->src = &hcon->hdev->bdaddr;
> conn->dst = &hcon->dst;
>
> @@ -1102,8 +1112,13 @@ static int l2cap_do_connect(struct sock *sk)
> }
> }
>
> - hcon = hci_connect(hdev, ACL_LINK, dst,
> + if (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA)
> + hcon = hci_connect(hdev, LE_LINK, dst,
> + l2cap_pi(sk)->sec_level, auth_type);
> + else
I think that "else if (l2cap_pi(sk)->psm)" is better here, we do not
want to permit go ahead with psm 0.
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
Hi Ville,
* Ville Tervo <[email protected]> [2010-10-18 16:02:53 +0300]:
> BLuetooth chips may have separate buffers for
> LE traffic. This patch add support to use LE
> buffers provided by the chip.
>
> Signed-off-by: Ville Tervo <[email protected]>
> ---
> include/net/bluetooth/hci.h | 2 +
> include/net/bluetooth/hci_core.h | 5 +++
> net/bluetooth/hci_conn.c | 6 +++
> net/bluetooth/hci_core.c | 74 +++++++++++++++++++++++++++++++++++--
> net/bluetooth/hci_event.c | 40 +++++++++++++++++++-
> 5 files changed, 121 insertions(+), 6 deletions(-)
>
> diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
> index 02055b9..b42edf0 100644
> --- a/include/net/bluetooth/hci.h
> +++ b/include/net/bluetooth/hci.h
> @@ -189,6 +189,8 @@ enum {
>
> #define LMP_EV4 0x01
> #define LMP_EV5 0x02
> +#define LMP_NO_BREDR 0x20
You are not using this one.
> +#define LMP_LE 0x40
>
> #define LMP_SNIFF_SUBR 0x02
> #define LMP_EDR_ESCO_2M 0x20
> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> index fc2aaee..326d290 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -103,15 +103,19 @@ struct hci_dev {
> atomic_t cmd_cnt;
> unsigned int acl_cnt;
> unsigned int sco_cnt;
> + unsigned int le_cnt;
>
> unsigned int acl_mtu;
> unsigned int sco_mtu;
> + unsigned int le_mtu;
> unsigned int acl_pkts;
> unsigned int sco_pkts;
> + unsigned int le_pkts;
>
> unsigned long cmd_last_tx;
> unsigned long acl_last_tx;
> unsigned long sco_last_tx;
> + unsigned long le_last_tx;
>
> struct workqueue_struct *workqueue;
>
> @@ -469,6 +473,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
> #define lmp_sniffsubr_capable(dev) ((dev)->features[5] & LMP_SNIFF_SUBR)
> #define lmp_esco_capable(dev) ((dev)->features[3] & LMP_ESCO)
> #define lmp_ssp_capable(dev) ((dev)->features[6] & LMP_SIMPLE_PAIR)
> +#define lmp_le_capable(dev) ((dev)->features[4] & LMP_LE)
>
> /* ----- HCI protocols ----- */
> struct hci_proto {
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index c1eb8e0..c7309e4 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -324,6 +324,11 @@ int hci_conn_del(struct hci_conn *conn)
>
> /* Unacked frames */
> hdev->acl_cnt += conn->sent;
> + } else if (conn->type == LE_LINK) {
> + if (hdev->le_pkts)
> + hdev->le_cnt += conn->sent;
> + else
> + hdev->acl_cnt += conn->sent;
> } else {
> struct hci_conn *acl = conn->link;
> if (acl) {
> @@ -409,6 +414,7 @@ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8
> return NULL;
>
> hci_le_connect(le);
> + hci_conn_hold(le);
>
This should be in 2/6, right?
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
* Ville Tervo <[email protected]> [2010-10-18 16:02:52 +0300]:
> Bluetooth V4.0 adds support for Low Energy (LE)
> connections. Specification introduses new set
> of hci commands to control LE connection.
> This patch adds logic to create, cancel and
> disconnect LE connections
>
> Signed-off-by: Ville Tervo <[email protected]>
> ---
> include/net/bluetooth/hci.h | 2 +
> include/net/bluetooth/hci_core.h | 21 +++++++--
> net/bluetooth/hci_conn.c | 51 +++++++++++++++++++-
> net/bluetooth/hci_event.c | 94 +++++++++++++++++++++++++++++++++++++-
> 4 files changed, 160 insertions(+), 8 deletions(-)
>
> diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
> index ee5beec..02055b9 100644
> --- a/include/net/bluetooth/hci.h
> +++ b/include/net/bluetooth/hci.h
> @@ -159,6 +159,8 @@ enum {
> #define SCO_LINK 0x00
> #define ACL_LINK 0x01
> #define ESCO_LINK 0x02
> +/* Low Energy links do not have defined link type. Use invented one */
> +#define LE_LINK 0x80
>
> /* LMP features */
> #define LMP_3SLOT 0x01
> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> index ebec8c9..fc2aaee 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -60,6 +60,7 @@ struct hci_conn_hash {
> spinlock_t lock;
> unsigned int acl_num;
> unsigned int sco_num;
> + unsigned int le_num;
> };
>
> struct bdaddr_list {
> @@ -272,20 +273,32 @@ static inline void hci_conn_hash_add(struct hci_dev *hdev, struct hci_conn *c)
> {
> struct hci_conn_hash *h = &hdev->conn_hash;
> list_add(&c->list, &h->list);
> - if (c->type == ACL_LINK)
> + switch (c->type) {
> + case ACL_LINK:
> h->acl_num++;
> - else
> + break;
> + case LE_LINK:
> + h->le_num++;
> + break;
> + default:
I would add a 'case SCO_LINK' here. It changes nothing actually, but
make switch easy to understand.
> h->sco_num++;
> + }
> }
>
> static inline void hci_conn_hash_del(struct hci_dev *hdev, struct hci_conn *c)
> {
> struct hci_conn_hash *h = &hdev->conn_hash;
> list_del(&c->list);
> - if (c->type == ACL_LINK)
> + switch (c->type) {
> + case ACL_LINK:
> h->acl_num--;
> - else
> + break;
> + case LE_LINK:
> + h->le_num--;
> + break;
> + default:
Same here.
> h->sco_num--;
> + }
> }
>
> static inline struct hci_conn *hci_conn_hash_lookup_handle(struct hci_dev *hdev,
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index 0b1e460..c1eb8e0 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -45,6 +45,32 @@
> #include <net/bluetooth/bluetooth.h>
> #include <net/bluetooth/hci_core.h>
>
> +void hci_le_connect(struct hci_conn *conn)
> +{
> + struct hci_dev *hdev = conn->hdev;
> + struct hci_cp_le_create_conn cp;
> +
> + conn->state = BT_CONNECT;
> + conn->out = 1;
> +
> + 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.conn_interval_min = cpu_to_le16(0x0008);
> + cp.conn_interval_max = cpu_to_le16(0x0100);
> + cp.supervision_timeout = cpu_to_le16(0x0064);
> + cp.min_ce_len = cpu_to_le16(0x0001);
> + cp.max_ce_len = cpu_to_le16(0x0001);
> +
> + hci_send_cmd(hdev, HCI_OP_LE_CREATE_CONN, sizeof(cp), &cp);
> +}
> +
> +static void hci_le_connect_cancel(struct hci_conn *conn)
> +{
> + hci_send_cmd(conn->hdev, HCI_OP_LE_CREATE_CONN_CANCEL, 0, NULL);
> +}
> +
> void hci_acl_connect(struct hci_conn *conn)
> {
> struct hci_dev *hdev = conn->hdev;
> @@ -192,8 +218,12 @@ static void hci_conn_timeout(unsigned long arg)
> switch (conn->state) {
> case BT_CONNECT:
> case BT_CONNECT2:
> - if (conn->type == ACL_LINK && conn->out)
> - hci_acl_connect_cancel(conn);
> + if (conn->out) {
> + if (conn->type == ACL_LINK)
> + hci_acl_connect_cancel(conn);
> + else if (conn->type == LE_LINK)
> + hci_le_connect_cancel(conn);
> + }
> break;
> case BT_CONFIG:
> case BT_CONNECTED:
> @@ -359,15 +389,30 @@ struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src)
> }
> EXPORT_SYMBOL(hci_get_route);
>
> -/* Create SCO or ACL connection.
> +/* Create SCO, ACL or LE connection.
> * Device _must_ be locked */
> struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8 sec_level, __u8 auth_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) {
> + le = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst);
> +
> + if (!le)
> + le = hci_conn_add(hdev, LE_LINK, dst);
> +
> + if (!le)
> + return NULL;
> +
> + hci_le_connect(le);
> +
> + return le;
> + }
> +
> if (!(acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst))) {
> if (!(acl = hci_conn_add(hdev, ACL_LINK, dst)))
> return NULL;
> diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
> index 84093b0..4061613 100644
> --- a/net/bluetooth/hci_event.c
> +++ b/net/bluetooth/hci_event.c
> @@ -822,6 +822,43 @@ static void hci_cs_exit_sniff_mode(struct hci_dev *hdev, __u8 status)
> hci_dev_unlock(hdev);
> }
>
> +static void hci_cs_le_create_conn(struct hci_dev *hdev, __u8 status)
> +{
> + struct hci_cp_le_create_conn *cp;
> + struct hci_conn *conn;
> +
> + BT_DBG("%s status 0x%x", hdev->name, status);
> +
> + cp = hci_sent_cmd_data(hdev, HCI_OP_LE_CREATE_CONN);
> + if (!cp)
> + return;
> +
> + hci_dev_lock(hdev);
> +
> + conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->peer_addr);
> +
> + BT_DBG("%s bdaddr %s conn %p", hdev->name, batostr(&cp->peer_addr),
> + conn);
> +
> + if (status) {
> + if (conn && conn->state == BT_CONNECT) {
> + conn->state = BT_CLOSED;
> + hci_proto_connect_cfm(conn, status);
> + hci_conn_del(conn);
> + }
> + } else {
> + if (!conn) {
> + conn = hci_conn_add(hdev, LE_LINK, &cp->peer_addr);
> + if (conAvoid things like that in your patch.n)
> + conn->out = 1;
> + else
> + BT_ERR("No memory for new connection");
> + }
> + }
> +
> + hci_dev_unlock(hdev);
> +}
> +
> static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
> {
> __u8 status = *((__u8 *) skb->data);
> @@ -1024,7 +1061,6 @@ static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff
> conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle));
> if (conn) {
> conn->state = BT_CLOSED;
> -
Avoid things like that in your patch.
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
Hi Claudio,
On Wed, Nov 10, 2010 at 05:53:02PM +0100, ext Claudio Takahasi wrote:
> Hi Ville,
>
> On Mon, Oct 25, 2010 at 10:21 AM, Ville Tervo <[email protected]> wrote:
> > BLuetooth chips may have separate buffers for
> > LE traffic. This patch add support to use LE
> > buffers provided by the chip.
> >
> > Signed-off-by: Ville Tervo <[email protected]>
> > ---
> > ?include/net/bluetooth/hci.h ? ? ?| ? ?1 +
> > ?include/net/bluetooth/hci_core.h | ? ?5 +++
> > ?net/bluetooth/hci_conn.c ? ? ? ? | ? ?5 +++
> > ?net/bluetooth/hci_core.c ? ? ? ? | ? 74 +++++++++++++++++++++++++++++++++++--
> > ?net/bluetooth/hci_event.c ? ? ? ?| ? 40 +++++++++++++++++++-
> > ?5 files changed, 119 insertions(+), 6 deletions(-)
> >
> > diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
> > index 02055b9..2103731 100644
> > --- a/include/net/bluetooth/hci.h
> > +++ b/include/net/bluetooth/hci.h
> > @@ -189,6 +189,7 @@ enum {
> >
> > ?#define LMP_EV4 ? ? ? ? ? ? ? ?0x01
> > ?#define LMP_EV5 ? ? ? ? ? ? ? ?0x02
> > +#define LMP_LE ? ? ? ? 0x40
> >
> > ?#define LMP_SNIFF_SUBR 0x02
> > ?#define LMP_EDR_ESCO_2M ? ? ? ?0x20
> > diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> > index 2b7f94a..e2d857a 100644
> > --- a/include/net/bluetooth/hci_core.h
> > +++ b/include/net/bluetooth/hci_core.h
> > @@ -103,15 +103,19 @@ struct hci_dev {
> > ? ? ? ?atomic_t ? ? ? ?cmd_cnt;
> > ? ? ? ?unsigned int ? ?acl_cnt;
> > ? ? ? ?unsigned int ? ?sco_cnt;
> > + ? ? ? unsigned int ? ?le_cnt;
> >
> > ? ? ? ?unsigned int ? ?acl_mtu;
> > ? ? ? ?unsigned int ? ?sco_mtu;
> > + ? ? ? unsigned int ? ?le_mtu;
> > ? ? ? ?unsigned int ? ?acl_pkts;
> > ? ? ? ?unsigned int ? ?sco_pkts;
> > + ? ? ? unsigned int ? ?le_pkts;
> >
> > ? ? ? ?unsigned long ? cmd_last_tx;
> > ? ? ? ?unsigned long ? acl_last_tx;
> > ? ? ? ?unsigned long ? sco_last_tx;
> > + ? ? ? unsigned long ? le_last_tx;
> >
> > ? ? ? ?struct workqueue_struct *workqueue;
> >
> > @@ -473,6 +477,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
> > ?#define lmp_sniffsubr_capable(dev) ((dev)->features[5] & LMP_SNIFF_SUBR)
> > ?#define lmp_esco_capable(dev) ? ? ?((dev)->features[3] & LMP_ESCO)
> > ?#define lmp_ssp_capable(dev) ? ? ? ((dev)->features[6] & LMP_SIMPLE_PAIR)
> > +#define lmp_le_capable(dev) ? ? ? ?((dev)->features[4] & LMP_LE)
> >
> > ?/* ----- HCI protocols ----- */
> > ?struct hci_proto {
> > diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> > index 0944c0c..ddc2e5e 100644
> > --- a/net/bluetooth/hci_conn.c
> > +++ b/net/bluetooth/hci_conn.c
> > @@ -324,6 +324,11 @@ int hci_conn_del(struct hci_conn *conn)
> >
> > ? ? ? ? ? ? ? ?/* Unacked frames */
> > ? ? ? ? ? ? ? ?hdev->acl_cnt += conn->sent;
> > + ? ? ? } else if (conn->type == LE_LINK) {
> > + ? ? ? ? ? ? ? if (hdev->le_pkts)
> > + ? ? ? ? ? ? ? ? ? ? ? hdev->le_cnt += conn->sent;
> > + ? ? ? ? ? ? ? else
> > + ? ? ? ? ? ? ? ? ? ? ? hdev->acl_cnt += conn->sent;
> > ? ? ? ?} else {
> > ? ? ? ? ? ? ? ?struct hci_conn *acl = conn->link;
> > ? ? ? ? ? ? ? ?if (acl) {
> > diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
> > index bc2a052..45c78c2 100644
> > --- a/net/bluetooth/hci_core.c
> > +++ b/net/bluetooth/hci_core.c
> > @@ -254,6 +254,14 @@ static void hci_init_req(struct hci_dev *hdev, unsigned long opt)
> > ? ? ? ?hci_send_cmd(hdev, HCI_OP_WRITE_CA_TIMEOUT, 2, ¶m);
> > ?}
> >
> > +static void hci_le_init_req(struct hci_dev *hdev, unsigned long opt)
> > +{
> > + ? ? ? BT_DBG("%s", hdev->name);
> > +
> > + ? ? ? /* Read LE buffer size */
> > + ? ? ? hci_send_cmd(hdev, HCI_OP_LE_READ_BUFFER_SIZE, 0, NULL);
> > +}
> > +
> > ?static void hci_scan_req(struct hci_dev *hdev, unsigned long opt)
> > ?{
> > ? ? ? ?__u8 scan = opt;
> > @@ -509,6 +517,10 @@ int hci_dev_open(__u16 dev)
> > ? ? ? ? ? ? ? ?ret = __hci_request(hdev, hci_init_req, 0,
> > ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?msecs_to_jiffies(HCI_INIT_TIMEOUT));
> >
> > + ? ? ? ? ? ? ? if (lmp_le_capable(hdev))
> > + ? ? ? ? ? ? ? ? ? ? ? ret = __hci_request(hdev, hci_le_init_req, 0,
> > + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? msecs_to_jiffies(HCI_INIT_TIMEOUT));
> > +
> > ? ? ? ? ? ? ? ?clear_bit(HCI_INIT, &hdev->flags);
> > ? ? ? ?}
> >
> > @@ -645,7 +657,7 @@ int hci_dev_reset(__u16 dev)
> > ? ? ? ? ? ? ? ?hdev->flush(hdev);
> >
> > ? ? ? ?atomic_set(&hdev->cmd_cnt, 1);
> > - ? ? ? hdev->acl_cnt = 0; hdev->sco_cnt = 0;
> > + ? ? ? hdev->acl_cnt = 0; hdev->sco_cnt = 0; hdev->le_cnt = 0;
> >
> > ? ? ? ?if (!test_bit(HCI_RAW, &hdev->flags))
> > ? ? ? ? ? ? ? ?ret = __hci_request(hdev, hci_reset_req, 0,
> > @@ -1456,8 +1468,25 @@ static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int
> > ? ? ? ?}
> >
> > ? ? ? ?if (conn) {
> > - ? ? ? ? ? ? ? int cnt = (type == ACL_LINK ? hdev->acl_cnt : hdev->sco_cnt);
> > - ? ? ? ? ? ? ? int q = cnt / num;
> > + ? ? ? ? ? ? ? int cnt, q;
> > +
> > + ? ? ? ? ? ? ? switch (conn->type) {
> > + ? ? ? ? ? ? ? case ACL_LINK:
> > + ? ? ? ? ? ? ? ? ? ? ? cnt = hdev->acl_cnt;
> > + ? ? ? ? ? ? ? ? ? ? ? break;
> > + ? ? ? ? ? ? ? case SCO_LINK:
> > + ? ? ? ? ? ? ? case ESCO_LINK:
> > + ? ? ? ? ? ? ? ? ? ? ? cnt = hdev->sco_cnt;
> > + ? ? ? ? ? ? ? ? ? ? ? break;
> > + ? ? ? ? ? ? ? case LE_LINK:
> > + ? ? ? ? ? ? ? ? ? ? ? cnt = hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
> > + ? ? ? ? ? ? ? ? ? ? ? break;
> > + ? ? ? ? ? ? ? default:
> > + ? ? ? ? ? ? ? ? ? ? ? cnt = 0;
> > + ? ? ? ? ? ? ? ? ? ? ? BT_ERR("Unknown link type");
> > + ? ? ? ? ? ? ? }
> > +
> > + ? ? ? ? ? ? ? q = cnt / num;
> > ? ? ? ? ? ? ? ?*quote = q ? q : 1;
> > ? ? ? ?} else
> > ? ? ? ? ? ? ? ?*quote = 0;
> > @@ -1556,6 +1585,40 @@ static inline void hci_sched_esco(struct hci_dev *hdev)
> > ? ? ? ?}
> > ?}
> >
> > +static inline void hci_sched_le(struct hci_dev *hdev)
> > +{
> > + ? ? ? struct hci_conn *conn;
> > + ? ? ? struct sk_buff *skb;
> > + ? ? ? int quote, cnt;
> > +
> > + ? ? ? BT_DBG("%s", hdev->name);
> > +
> > + ? ? ? if (!test_bit(HCI_RAW, &hdev->flags)) {
> > + ? ? ? ? ? ? ? /* ACL tx timeout must be longer than maximum
> > + ? ? ? ? ? ? ? ?* link supervision timeout (40.9 seconds) */
> > + ? ? ? ? ? ? ? if (!hdev->le_cnt &&
> > + ? ? ? ? ? ? ? ? ? time_after(jiffies, hdev->le_last_tx + HZ * 45))
> > + ? ? ? ? ? ? ? ? ? ? ? hci_acl_tx_to(hdev);
> > + ? ? ? }
>
> It seems that the ACL tx timeout is causing some problems: BR/EDR and
> LE connections are not working properly on macbooks! Don't ask me why
> on macbooks only! But I double checked. I tested your branch and also
> bluetooth-next + LE patches and both are not working as expected. I
> didn't have time to investigate it, do you have any clue?
>
> For BR/EDR I am receiving "killing stalled ACL connection" messages
> for all connections.
>
Yes you are right. I found this also yesterday and I have a patch to fix it
already. I'll clean it up and submit later today.
--
Ville
SGkgVmlsbGUsCgpPbiBNb24sIE9jdCAyNSwgMjAxMCBhdCAxMDoyMSBBTSwgVmlsbGUgVGVydm8g
PHZpbGxlLnRlcnZvQG5va2lhLmNvbT4gd3JvdGU6Cj4gQkx1ZXRvb3RoIGNoaXBzIG1heSBoYXZl
IHNlcGFyYXRlIGJ1ZmZlcnMgZm9yCj4gTEUgdHJhZmZpYy4gVGhpcyBwYXRjaCBhZGQgc3VwcG9y
dCB0byB1c2UgTEUKPiBidWZmZXJzIHByb3ZpZGVkIGJ5IHRoZSBjaGlwLgo+Cj4gU2lnbmVkLW9m
Zi1ieTogVmlsbGUgVGVydm8gPHZpbGxlLnRlcnZvQG5va2lhLmNvbT4KPiAtLS0KPiDCoGluY2x1
ZGUvbmV0L2JsdWV0b290aC9oY2kuaCDCoCDCoCDCoHwgwqAgwqAxICsKPiDCoGluY2x1ZGUvbmV0
L2JsdWV0b290aC9oY2lfY29yZS5oIHwgwqAgwqA1ICsrKwo+IMKgbmV0L2JsdWV0b290aC9oY2lf
Y29ubi5jIMKgIMKgIMKgIMKgIHwgwqAgwqA1ICsrKwo+IMKgbmV0L2JsdWV0b290aC9oY2lfY29y
ZS5jIMKgIMKgIMKgIMKgIHwgwqAgNzQgKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
KystLQo+IMKgbmV0L2JsdWV0b290aC9oY2lfZXZlbnQuYyDCoCDCoCDCoCDCoHwgwqAgNDAgKysr
KysrKysrKysrKysrKysrKy0KPiDCoDUgZmlsZXMgY2hhbmdlZCwgMTE5IGluc2VydGlvbnMoKyks
IDYgZGVsZXRpb25zKC0pCj4KPiBkaWZmIC0tZ2l0IGEvaW5jbHVkZS9uZXQvYmx1ZXRvb3RoL2hj
aS5oIGIvaW5jbHVkZS9uZXQvYmx1ZXRvb3RoL2hjaS5oCj4gaW5kZXggMDIwNTViOS4uMjEwMzcz
MSAxMDA2NDQKPiAtLS0gYS9pbmNsdWRlL25ldC9ibHVldG9vdGgvaGNpLmgKPiArKysgYi9pbmNs
dWRlL25ldC9ibHVldG9vdGgvaGNpLmgKPiBAQCAtMTg5LDYgKzE4OSw3IEBAIGVudW0gewo+Cj4g
wqAjZGVmaW5lIExNUF9FVjQgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAweDAxCj4gwqAjZGVmaW5l
IExNUF9FVjUgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAweDAyCj4gKyNkZWZpbmUgTE1QX0xFIMKg
IMKgIMKgIMKgIDB4NDAKPgo+IMKgI2RlZmluZSBMTVBfU05JRkZfU1VCUiAweDAyCj4gwqAjZGVm
aW5lIExNUF9FRFJfRVNDT18yTSDCoCDCoCDCoCDCoDB4MjAKPiBkaWZmIC0tZ2l0IGEvaW5jbHVk
ZS9uZXQvYmx1ZXRvb3RoL2hjaV9jb3JlLmggYi9pbmNsdWRlL25ldC9ibHVldG9vdGgvaGNpX2Nv
cmUuaAo+IGluZGV4IDJiN2Y5NGEuLmUyZDg1N2EgMTAwNjQ0Cj4gLS0tIGEvaW5jbHVkZS9uZXQv
Ymx1ZXRvb3RoL2hjaV9jb3JlLmgKPiArKysgYi9pbmNsdWRlL25ldC9ibHVldG9vdGgvaGNpX2Nv
cmUuaAo+IEBAIC0xMDMsMTUgKzEwMywxOSBAQCBzdHJ1Y3QgaGNpX2RldiB7Cj4gwqAgwqAgwqAg
wqBhdG9taWNfdCDCoCDCoCDCoCDCoGNtZF9jbnQ7Cj4gwqAgwqAgwqAgwqB1bnNpZ25lZCBpbnQg
wqAgwqBhY2xfY250Owo+IMKgIMKgIMKgIMKgdW5zaWduZWQgaW50IMKgIMKgc2NvX2NudDsKPiAr
IMKgIMKgIMKgIHVuc2lnbmVkIGludCDCoCDCoGxlX2NudDsKPgo+IMKgIMKgIMKgIMKgdW5zaWdu
ZWQgaW50IMKgIMKgYWNsX210dTsKPiDCoCDCoCDCoCDCoHVuc2lnbmVkIGludCDCoCDCoHNjb19t
dHU7Cj4gKyDCoCDCoCDCoCB1bnNpZ25lZCBpbnQgwqAgwqBsZV9tdHU7Cj4gwqAgwqAgwqAgwqB1
bnNpZ25lZCBpbnQgwqAgwqBhY2xfcGt0czsKPiDCoCDCoCDCoCDCoHVuc2lnbmVkIGludCDCoCDC
oHNjb19wa3RzOwo+ICsgwqAgwqAgwqAgdW5zaWduZWQgaW50IMKgIMKgbGVfcGt0czsKPgo+IMKg
IMKgIMKgIMKgdW5zaWduZWQgbG9uZyDCoCBjbWRfbGFzdF90eDsKPiDCoCDCoCDCoCDCoHVuc2ln
bmVkIGxvbmcgwqAgYWNsX2xhc3RfdHg7Cj4gwqAgwqAgwqAgwqB1bnNpZ25lZCBsb25nIMKgIHNj
b19sYXN0X3R4Owo+ICsgwqAgwqAgwqAgdW5zaWduZWQgbG9uZyDCoCBsZV9sYXN0X3R4Owo+Cj4g
wqAgwqAgwqAgwqBzdHJ1Y3Qgd29ya3F1ZXVlX3N0cnVjdCAqd29ya3F1ZXVlOwo+Cj4gQEAgLTQ3
Myw2ICs0NzcsNyBAQCB2b2lkIGhjaV9jb25uX2RlbF9zeXNmcyhzdHJ1Y3QgaGNpX2Nvbm4gKmNv
bm4pOwo+IMKgI2RlZmluZSBsbXBfc25pZmZzdWJyX2NhcGFibGUoZGV2KSAoKGRldiktPmZlYXR1
cmVzWzVdICYgTE1QX1NOSUZGX1NVQlIpCj4gwqAjZGVmaW5lIGxtcF9lc2NvX2NhcGFibGUoZGV2
KSDCoCDCoCDCoCgoZGV2KS0+ZmVhdHVyZXNbM10gJiBMTVBfRVNDTykKPiDCoCNkZWZpbmUgbG1w
X3NzcF9jYXBhYmxlKGRldikgwqAgwqAgwqAgKChkZXYpLT5mZWF0dXJlc1s2XSAmIExNUF9TSU1Q
TEVfUEFJUikKPiArI2RlZmluZSBsbXBfbGVfY2FwYWJsZShkZXYpIMKgIMKgIMKgIMKgKChkZXYp
LT5mZWF0dXJlc1s0XSAmIExNUF9MRSkKPgo+IMKgLyogLS0tLS0gSENJIHByb3RvY29scyAtLS0t
LSAqLwo+IMKgc3RydWN0IGhjaV9wcm90byB7Cj4gZGlmZiAtLWdpdCBhL25ldC9ibHVldG9vdGgv
aGNpX2Nvbm4uYyBiL25ldC9ibHVldG9vdGgvaGNpX2Nvbm4uYwo+IGluZGV4IDA5NDRjMGMuLmRk
YzJlNWUgMTAwNjQ0Cj4gLS0tIGEvbmV0L2JsdWV0b290aC9oY2lfY29ubi5jCj4gKysrIGIvbmV0
L2JsdWV0b290aC9oY2lfY29ubi5jCj4gQEAgLTMyNCw2ICszMjQsMTEgQEAgaW50IGhjaV9jb25u
X2RlbChzdHJ1Y3QgaGNpX2Nvbm4gKmNvbm4pCj4KPiDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoC8q
IFVuYWNrZWQgZnJhbWVzICovCj4gwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBoZGV2LT5hY2xfY250
ICs9IGNvbm4tPnNlbnQ7Cj4gKyDCoCDCoCDCoCB9IGVsc2UgaWYgKGNvbm4tPnR5cGUgPT0gTEVf
TElOSykgewo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgaWYgKGhkZXYtPmxlX3BrdHMpCj4gKyDC
oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCBoZGV2LT5sZV9jbnQgKz0gY29ubi0+c2Vu
dDsKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGVsc2UKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKg
IMKgIMKgIMKgIMKgIGhkZXYtPmFjbF9jbnQgKz0gY29ubi0+c2VudDsKPiDCoCDCoCDCoCDCoH0g
ZWxzZSB7Cj4gwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBzdHJ1Y3QgaGNpX2Nvbm4gKmFjbCA9IGNv
bm4tPmxpbms7Cj4gwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBpZiAoYWNsKSB7Cj4gZGlmZiAtLWdp
dCBhL25ldC9ibHVldG9vdGgvaGNpX2NvcmUuYyBiL25ldC9ibHVldG9vdGgvaGNpX2NvcmUuYwo+
IGluZGV4IGJjMmEwNTIuLjQ1Yzc4YzIgMTAwNjQ0Cj4gLS0tIGEvbmV0L2JsdWV0b290aC9oY2lf
Y29yZS5jCj4gKysrIGIvbmV0L2JsdWV0b290aC9oY2lfY29yZS5jCj4gQEAgLTI1NCw2ICsyNTQs
MTQgQEAgc3RhdGljIHZvaWQgaGNpX2luaXRfcmVxKHN0cnVjdCBoY2lfZGV2ICpoZGV2LCB1bnNp
Z25lZCBsb25nIG9wdCkKPiDCoCDCoCDCoCDCoGhjaV9zZW5kX2NtZChoZGV2LCBIQ0lfT1BfV1JJ
VEVfQ0FfVElNRU9VVCwgMiwgJnBhcmFtKTsKPiDCoH0KPgo+ICtzdGF0aWMgdm9pZCBoY2lfbGVf
aW5pdF9yZXEoc3RydWN0IGhjaV9kZXYgKmhkZXYsIHVuc2lnbmVkIGxvbmcgb3B0KQo+ICt7Cj4g
KyDCoCDCoCDCoCBCVF9EQkcoIiVzIiwgaGRldi0+bmFtZSk7Cj4gKwo+ICsgwqAgwqAgwqAgLyog
UmVhZCBMRSBidWZmZXIgc2l6ZSAqLwo+ICsgwqAgwqAgwqAgaGNpX3NlbmRfY21kKGhkZXYsIEhD
SV9PUF9MRV9SRUFEX0JVRkZFUl9TSVpFLCAwLCBOVUxMKTsKPiArfQo+ICsKPiDCoHN0YXRpYyB2
b2lkIGhjaV9zY2FuX3JlcShzdHJ1Y3QgaGNpX2RldiAqaGRldiwgdW5zaWduZWQgbG9uZyBvcHQp
Cj4gwqB7Cj4gwqAgwqAgwqAgwqBfX3U4IHNjYW4gPSBvcHQ7Cj4gQEAgLTUwOSw2ICs1MTcsMTAg
QEAgaW50IGhjaV9kZXZfb3BlbihfX3UxNiBkZXYpCj4gwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBy
ZXQgPSBfX2hjaV9yZXF1ZXN0KGhkZXYsIGhjaV9pbml0X3JlcSwgMCwKPiDCoCDCoCDCoCDCoCDC
oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoG1zZWNzX3RvX2pp
ZmZpZXMoSENJX0lOSVRfVElNRU9VVCkpOwo+Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCBpZiAo
bG1wX2xlX2NhcGFibGUoaGRldikpCj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC
oCByZXQgPSBfX2hjaV9yZXF1ZXN0KGhkZXYsIGhjaV9sZV9pbml0X3JlcSwgMCwKPiArIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIG1zZWNz
X3RvX2ppZmZpZXMoSENJX0lOSVRfVElNRU9VVCkpOwo+ICsKPiDCoCDCoCDCoCDCoCDCoCDCoCDC
oCDCoGNsZWFyX2JpdChIQ0lfSU5JVCwgJmhkZXYtPmZsYWdzKTsKPiDCoCDCoCDCoCDCoH0KPgo+
IEBAIC02NDUsNyArNjU3LDcgQEAgaW50IGhjaV9kZXZfcmVzZXQoX191MTYgZGV2KQo+IMKgIMKg
IMKgIMKgIMKgIMKgIMKgIMKgaGRldi0+Zmx1c2goaGRldik7Cj4KPiDCoCDCoCDCoCDCoGF0b21p
Y19zZXQoJmhkZXYtPmNtZF9jbnQsIDEpOwo+IC0gwqAgwqAgwqAgaGRldi0+YWNsX2NudCA9IDA7
IGhkZXYtPnNjb19jbnQgPSAwOwo+ICsgwqAgwqAgwqAgaGRldi0+YWNsX2NudCA9IDA7IGhkZXYt
PnNjb19jbnQgPSAwOyBoZGV2LT5sZV9jbnQgPSAwOwo+Cj4gwqAgwqAgwqAgwqBpZiAoIXRlc3Rf
Yml0KEhDSV9SQVcsICZoZGV2LT5mbGFncykpCj4gwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqByZXQg
PSBfX2hjaV9yZXF1ZXN0KGhkZXYsIGhjaV9yZXNldF9yZXEsIDAsCj4gQEAgLTE0NTYsOCArMTQ2
OCwyNSBAQCBzdGF0aWMgaW5saW5lIHN0cnVjdCBoY2lfY29ubiAqaGNpX2xvd19zZW50KHN0cnVj
dCBoY2lfZGV2ICpoZGV2LCBfX3U4IHR5cGUsIGludAo+IMKgIMKgIMKgIMKgfQo+Cj4gwqAgwqAg
wqAgwqBpZiAoY29ubikgewo+IC0gwqAgwqAgwqAgwqAgwqAgwqAgwqAgaW50IGNudCA9ICh0eXBl
ID09IEFDTF9MSU5LID8gaGRldi0+YWNsX2NudCA6IGhkZXYtPnNjb19jbnQpOwo+IC0gwqAgwqAg
wqAgwqAgwqAgwqAgwqAgaW50IHEgPSBjbnQgLyBudW07Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDC
oCBpbnQgY250LCBxOwo+ICsKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIHN3aXRjaCAoY29ubi0+
dHlwZSkgewo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgY2FzZSBBQ0xfTElOSzoKPiArIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGNudCA9IGhkZXYtPmFjbF9jbnQ7Cj4gKyDCoCDC
oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCBicmVhazsKPiArIMKgIMKgIMKgIMKgIMKgIMKg
IMKgIGNhc2UgU0NPX0xJTks6Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCBjYXNlIEVTQ09fTElO
SzoKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGNudCA9IGhkZXYtPnNjb19j
bnQ7Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCBicmVhazsKPiArIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIGNhc2UgTEVfTElOSzoKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg
IMKgIMKgIMKgIGNudCA9IGhkZXYtPmxlX210dSA/IGhkZXYtPmxlX2NudCA6IGhkZXYtPmFjbF9j
bnQ7Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCBicmVhazsKPiArIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIGRlZmF1bHQ6Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC
oCDCoCBjbnQgPSAwOwo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgQlRfRVJS
KCJVbmtub3duIGxpbmsgdHlwZSIpOwo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgfQo+ICsKPiAr
IMKgIMKgIMKgIMKgIMKgIMKgIMKgIHEgPSBjbnQgLyBudW07Cj4gwqAgwqAgwqAgwqAgwqAgwqAg
wqAgwqAqcXVvdGUgPSBxID8gcSA6IDE7Cj4gwqAgwqAgwqAgwqB9IGVsc2UKPiDCoCDCoCDCoCDC
oCDCoCDCoCDCoCDCoCpxdW90ZSA9IDA7Cj4gQEAgLTE1NTYsNiArMTU4NSw0MCBAQCBzdGF0aWMg
aW5saW5lIHZvaWQgaGNpX3NjaGVkX2VzY28oc3RydWN0IGhjaV9kZXYgKmhkZXYpCj4gwqAgwqAg
wqAgwqB9Cj4gwqB9Cj4KPiArc3RhdGljIGlubGluZSB2b2lkIGhjaV9zY2hlZF9sZShzdHJ1Y3Qg
aGNpX2RldiAqaGRldikKPiArewo+ICsgwqAgwqAgwqAgc3RydWN0IGhjaV9jb25uICpjb25uOwo+
ICsgwqAgwqAgwqAgc3RydWN0IHNrX2J1ZmYgKnNrYjsKPiArIMKgIMKgIMKgIGludCBxdW90ZSwg
Y250Owo+ICsKPiArIMKgIMKgIMKgIEJUX0RCRygiJXMiLCBoZGV2LT5uYW1lKTsKPiArCj4gKyDC
oCDCoCDCoCBpZiAoIXRlc3RfYml0KEhDSV9SQVcsICZoZGV2LT5mbGFncykpIHsKPiArIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIC8qIEFDTCB0eCB0aW1lb3V0IG11c3QgYmUgbG9uZ2VyIHRoYW4gbWF4
aW11bQo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAqIGxpbmsgc3VwZXJ2aXNpb24gdGltZW91
dCAoNDAuOSBzZWNvbmRzKSAqLwo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgaWYgKCFoZGV2LT5s
ZV9jbnQgJiYKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIHRpbWVfYWZ0ZXIoamlmZmll
cywgaGRldi0+bGVfbGFzdF90eCArIEhaICogNDUpKQo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAg
wqAgwqAgwqAgwqAgaGNpX2FjbF90eF90byhoZGV2KTsKPiArIMKgIMKgIMKgIH0KCkl0IHNlZW1z
IHRoYXQgdGhlIEFDTCB0eCB0aW1lb3V0IGlzIGNhdXNpbmcgc29tZSBwcm9ibGVtczogQlIvRURS
IGFuZApMRSBjb25uZWN0aW9ucyBhcmUgbm90IHdvcmtpbmcgcHJvcGVybHkgb24gbWFjYm9va3Mh
IERvbid0IGFzayBtZSB3aHkKb24gbWFjYm9va3Mgb25seSEgQnV0IEkgZG91YmxlIGNoZWNrZWQu
IEkgdGVzdGVkIHlvdXIgYnJhbmNoIGFuZCBhbHNvCmJsdWV0b290aC1uZXh0ICsgTEUgcGF0Y2hl
cyBhbmQgYm90aCBhcmUgbm90IHdvcmtpbmcgYXMgZXhwZWN0ZWQuIEkKZGlkbid0IGhhdmUgdGlt
ZSB0byBpbnZlc3RpZ2F0ZSBpdCwgZG8geW91IGhhdmUgYW55IGNsdWU/CgpGb3IgQlIvRURSIEkg
YW0gcmVjZWl2aW5nICJraWxsaW5nIHN0YWxsZWQgQUNMIGNvbm5lY3Rpb24iIG1lc3NhZ2VzCmZv
ciBhbGwgY29ubmVjdGlvbnMuCgoKUmVnYXJkcywKQ2xhdWRpbwoKCj4gKwo+ICsgwqAgwqAgwqAg
Y250ID0gaGRldi0+bGVfcGt0cyA/IGhkZXYtPmxlX2NudCA6IGhkZXYtPmFjbF9jbnQ7Cj4gKyDC
oCDCoCDCoCB3aGlsZSAoY250ICYmIChjb25uID0gaGNpX2xvd19zZW50KGhkZXYsIExFX0xJTkss
ICZxdW90ZSkpKSB7Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCB3aGlsZSAocXVvdGUtLSAmJiAo
c2tiID0gc2tiX2RlcXVldWUoJmNvbm4tPmRhdGFfcSkpKSB7Cj4gKyDCoCDCoCDCoCDCoCDCoCDC
oCDCoCDCoCDCoCDCoCDCoCBCVF9EQkcoInNrYiAlcCBsZW4gJWQiLCBza2IsIHNrYi0+bGVuKTsK
PiArCj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCBoY2lfc2VuZF9mcmFtZShz
a2IpOwo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgaGRldi0+bGVfbGFzdF90
eCA9IGppZmZpZXM7Cj4gKwo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgY250
LS07Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCBjb25uLT5zZW50Kys7Cj4g
KyDCoCDCoCDCoCDCoCDCoCDCoCDCoCB9Cj4gKyDCoCDCoCDCoCB9Cj4gKyDCoCDCoCDCoCBpZiAo
aGRldi0+bGVfcGt0cykKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGhkZXYtPmxlX2NudCA9IGNu
dDsKPiArIMKgIMKgIMKgIGVsc2UKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGhkZXYtPmFjbF9j
bnQgPSBjbnQ7Cj4gK30KPiArCj4gwqBzdGF0aWMgdm9pZCBoY2lfdHhfdGFzayh1bnNpZ25lZCBs
b25nIGFyZykKPiDCoHsKPiDCoCDCoCDCoCDCoHN0cnVjdCBoY2lfZGV2ICpoZGV2ID0gKHN0cnVj
dCBoY2lfZGV2ICopIGFyZzsKPiBAQCAtMTU2Myw3ICsxNjI2LDggQEAgc3RhdGljIHZvaWQgaGNp
X3R4X3Rhc2sodW5zaWduZWQgbG9uZyBhcmcpCj4KPiDCoCDCoCDCoCDCoHJlYWRfbG9jaygmaGNp
X3Rhc2tfbG9jayk7Cj4KPiAtIMKgIMKgIMKgIEJUX0RCRygiJXMgYWNsICVkIHNjbyAlZCIsIGhk
ZXYtPm5hbWUsIGhkZXYtPmFjbF9jbnQsIGhkZXYtPnNjb19jbnQpOwo+ICsgwqAgwqAgwqAgQlRf
REJHKCIlcyBhY2wgJWQgc2NvICVkIGxlICVkIiwgaGRldi0+bmFtZSwgaGRldi0+YWNsX2NudCwK
PiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGhkZXYtPnNjb19jbnQsIGhkZXYtPmxlX2NudCk7Cj4K
PiDCoCDCoCDCoCDCoC8qIFNjaGVkdWxlIHF1ZXVlcyBhbmQgc2VuZCBzdHVmZiB0byBIQ0kgZHJp
dmVyICovCj4KPiBAQCAtMTU3Myw2ICsxNjM3LDggQEAgc3RhdGljIHZvaWQgaGNpX3R4X3Rhc2so
dW5zaWduZWQgbG9uZyBhcmcpCj4KPiDCoCDCoCDCoCDCoGhjaV9zY2hlZF9lc2NvKGhkZXYpOwo+
Cj4gKyDCoCDCoCDCoCBoY2lfc2NoZWRfbGUoaGRldik7Cj4gKwo+IMKgIMKgIMKgIMKgLyogU2Vu
ZCBuZXh0IHF1ZXVlZCByYXcgKHVua25vd24gdHlwZSkgcGFja2V0ICovCj4gwqAgwqAgwqAgwqB3
aGlsZSAoKHNrYiA9IHNrYl9kZXF1ZXVlKCZoZGV2LT5yYXdfcSkpKQo+IMKgIMKgIMKgIMKgIMKg
IMKgIMKgIMKgaGNpX3NlbmRfZnJhbWUoc2tiKTsKPiBkaWZmIC0tZ2l0IGEvbmV0L2JsdWV0b290
aC9oY2lfZXZlbnQuYyBiL25ldC9ibHVldG9vdGgvaGNpX2V2ZW50LmMKPiBpbmRleCA5MmM4NjIx
Li5kYTIzNTAyIDEwMDY0NAo+IC0tLSBhL25ldC9ibHVldG9vdGgvaGNpX2V2ZW50LmMKPiArKysg
Yi9uZXQvYmx1ZXRvb3RoL2hjaV9ldmVudC5jCj4gQEAgLTUzOSw2ICs1MzksMjYgQEAgc3RhdGlj
IHZvaWQgaGNpX2NjX3JlYWRfYmRfYWRkcihzdHJ1Y3QgaGNpX2RldiAqaGRldiwgc3RydWN0IHNr
X2J1ZmYgKnNrYikKPiDCoCDCoCDCoCDCoGhjaV9yZXFfY29tcGxldGUoaGRldiwgcnAtPnN0YXR1
cyk7Cj4gwqB9Cj4KPiArc3RhdGljIHZvaWQgaGNpX2NjX2xlX3JlYWRfYnVmZmVyX3NpemUoc3Ry
dWN0IGhjaV9kZXYgKmhkZXYsCj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC
oCDCoCDCoCDCoCDCoCDCoCDCoCDCoHN0cnVjdCBza19idWZmICpza2IpCj4gK3sKPiArIMKgIMKg
IMKgIHN0cnVjdCBoY2lfcnBfbGVfcmVhZF9idWZmZXJfc2l6ZSAqcnAgPSAodm9pZCAqKSBza2It
PmRhdGE7Cj4gKwo+ICsgwqAgwqAgwqAgQlRfREJHKCIlcyBzdGF0dXMgMHgleCIsIGhkZXYtPm5h
bWUsIHJwLT5zdGF0dXMpOwo+ICsKPiArIMKgIMKgIMKgIGlmIChycC0+c3RhdHVzKQo+ICsgwqAg
wqAgwqAgwqAgwqAgwqAgwqAgcmV0dXJuOwo+ICsKPiArIMKgIMKgIMKgIGhkZXYtPmxlX210dSA9
IF9fbGUxNl90b19jcHUocnAtPmxlX210dSk7Cj4gKyDCoCDCoCDCoCBoZGV2LT5sZV9wa3RzID0g
cnAtPmxlX21heF9wa3Q7Cj4gKwo+ICsgwqAgwqAgwqAgaGRldi0+bGVfY250ID0gaGRldi0+bGVf
cGt0czsKPiArCj4gKyDCoCDCoCDCoCBCVF9EQkcoIiVzIGxlIG10dSAlZDolZCIsIGhkZXYtPm5h
bWUsIGhkZXYtPmxlX210dSwgaGRldi0+bGVfcGt0cyk7Cj4gKwo+ICsgwqAgwqAgwqAgaGNpX3Jl
cV9jb21wbGV0ZShoZGV2LCBycC0+c3RhdHVzKTsKPiArfQo+ICsKPiDCoHN0YXRpYyBpbmxpbmUg
dm9pZCBoY2lfY3NfaW5xdWlyeShzdHJ1Y3QgaGNpX2RldiAqaGRldiwgX191OCBzdGF0dXMpCj4g
wqB7Cj4gwqAgwqAgwqAgwqBCVF9EQkcoIiVzIHN0YXR1cyAweCV4IiwgaGRldi0+bmFtZSwgc3Rh
dHVzKTsKPiBAQCAtMTM1Myw2ICsxMzczLDEwIEBAIHN0YXRpYyBpbmxpbmUgdm9pZCBoY2lfY21k
X2NvbXBsZXRlX2V2dChzdHJ1Y3QgaGNpX2RldiAqaGRldiwgc3RydWN0IHNrX2J1ZmYgKnNrCj4g
wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBoY2lfY2NfcmVhZF9iZF9hZGRyKGhkZXYsIHNrYik7Cj4g
wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqBicmVhazsKPgo+ICsgwqAgwqAgwqAgY2FzZSBIQ0lfT1Bf
TEVfUkVBRF9CVUZGRVJfU0laRToKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGhjaV9jY19sZV9y
ZWFkX2J1ZmZlcl9zaXplKGhkZXYsIHNrYik7Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCBicmVh
azsKPiArCj4gwqAgwqAgwqAgwqBkZWZhdWx0Ogo+IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgQlRf
REJHKCIlcyBvcGNvZGUgMHgleCIsIGhkZXYtPm5hbWUsIG9wY29kZSk7Cj4gwqAgwqAgwqAgwqAg
wqAgwqAgwqAgwqBicmVhazsKPiBAQCAtMTQ5MCwxMCArMTUxNCwyMiBAQCBzdGF0aWMgaW5saW5l
IHZvaWQgaGNpX251bV9jb21wX3BrdHNfZXZ0KHN0cnVjdCBoY2lfZGV2ICpoZGV2LCBzdHJ1Y3Qg
c2tfYnVmZiAqcwo+IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgY29ubi0+c2Vu
dCAtPSBjb3VudDsKPgo+IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgaWYgKGNv
bm4tPnR5cGUgPT0gQUNMX0xJTkspIHsKPiAtIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIGlmICgoaGRldi0+YWNsX2NudCArPSBjb3VudCkgPiBoZGV2LT5hY2xf
cGt0cykKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGhk
ZXYtPmFjbF9jbnQgKz0gY291bnQ7Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC
oCDCoCDCoCDCoCDCoCBpZiAoaGRldi0+YWNsX2NudCA+IGhkZXYtPmFjbF9wa3RzKQo+IMKgIMKg
IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgaGRl
di0+YWNsX2NudCA9IGhkZXYtPmFjbF9wa3RzOwo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg
wqAgwqAgwqAgfSBlbHNlIGlmIChjb25uLT50eXBlID09IExFX0xJTkspIHsKPiArIMKgIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGlmIChoZGV2LT5sZV9wa3RzKSB7
Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC
oCDCoCBoZGV2LT5sZV9jbnQgKz0gY291bnQ7Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC
oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCBpZiAoaGRldi0+bGVfY250ID4gaGRldi0+
bGVfcGt0cykKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGhkZXYtPmxlX2NudCA9IGhkZXYtPmxlX3BrdHM7Cj4g
KyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCB9IGVsc2Ugewo+
ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg
wqAgaGRldi0+YWNsX2NudCArPSBjb3VudDsKPiArIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGlmIChoZGV2LT5hY2xfY250ID4gaGRldi0+
YWNsX3BrdHMpCj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDC
oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCBoZGV2LT5hY2xfY250ID0gaGRldi0+YWNsX3BrdHM7
Cj4gKyDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCB9Cj4gwqAg
wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqB9IGVsc2Ugewo+IC0gwqAgwqAgwqAgwqAg
wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgaWYgKChoZGV2LT5zY29fY250ICs9IGNv
dW50KSA+IGhkZXYtPnNjb19wa3RzKQo+ICsgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg
wqAgwqAgwqAgwqAgwqAgaGRldi0+c2NvX2NudCArPSBjb3VudDsKPiArIMKgIMKgIMKgIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIGlmIChoZGV2LT5zY29fY250ID4gaGRldi0+
c2NvX3BrdHMpCj4gwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg
wqAgwqAgwqAgwqAgwqBoZGV2LT5zY29fY250ID0gaGRldi0+c2NvX3BrdHM7Cj4gwqAgwqAgwqAg
wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqB9Cj4gwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqB9Cj4g
LS0KPiAxLjcuMQo+Cg==