Bluetooth: Use LE buffers for LE traffic
authorVille Tervo <ville.tervo@nokia.com>
Fri, 11 Feb 2011 01:38:48 +0000 (22:38 -0300)
committerGustavo F. Padovan <padovan@profusion.mobi>
Wed, 16 Feb 2011 19:32:51 +0000 (16:32 -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 <ville.tervo@nokia.com>
Acked-by: Marcel Holtmann <marcel@holtmann.org>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
include/net/bluetooth/hci_core.h
net/bluetooth/hci_conn.c
net/bluetooth/hci_core.c
net/bluetooth/hci_event.c

index f434e96ce020a29eb7e531a69769106610ac9c42..d30b93c82fd4e4728e516626ca9b9c933e88c8d3 100644 (file)
@@ -123,15 +123,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;
 
@@ -521,6 +525,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
 #define lmp_esco_capable(dev)      ((dev)->features[3] & LMP_ESCO)
 #define lmp_ssp_capable(dev)       ((dev)->features[6] & LMP_SIMPLE_PAIR)
 #define lmp_no_flush_capable(dev)  ((dev)->features[6] & LMP_NO_FLUSH)
+#define lmp_le_capable(dev)        ((dev)->features[4] & LMP_LE)
 
 /* ----- HCI protocols ----- */
 struct hci_proto {
index d0c470c18f9df8c8d60eff7cdab7b20ea63e8b7b..aecd78e6cceb06ca163e7de53d660dec51eb2e0d 100644 (file)
@@ -326,6 +326,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) {
index 2f003224d2ea9e4d9727c643f03ad3ec35c17b7f..92960532dea48d3435058c73de19848bcf019e90 100644 (file)
@@ -263,6 +263,14 @@ static void hci_init_req(struct hci_dev *hdev, unsigned long opt)
        hci_send_cmd(hdev, HCI_OP_DELETE_STORED_LINK_KEY, sizeof(cp), &cp);
 }
 
+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;
@@ -529,6 +537,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);
        }
 
@@ -671,7 +683,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,
@@ -1672,8 +1684,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;
@@ -1772,6 +1801,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)) {
+               /* LE 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, &quote))) {
+               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;
@@ -1779,7 +1842,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 */
 
@@ -1789,6 +1853,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);
index 47c6e9316ce88ea373dc12cfdf6fbc29971d80f0..3155ad588076b583d4c66ac72461e8cee5baaa67 100644 (file)
@@ -776,6 +776,25 @@ static void hci_cc_pin_code_neg_reply(struct hci_dev *hdev, struct sk_buff *skb)
                mgmt_pin_code_neg_reply_complete(hdev->id, &rp->bdaddr,
                                                                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, HCI_OP_LE_READ_BUFFER_SIZE, rp->status);
+}
 
 static inline void hci_cs_inquiry(struct hci_dev *hdev, __u8 status)
 {
@@ -1704,6 +1723,10 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk
                hci_cc_pin_code_neg_reply(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;
@@ -1849,6 +1872,16 @@ static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *s
                                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 {
                                hdev->sco_cnt += count;
                                if (hdev->sco_cnt > hdev->sco_pkts)