diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='sco.*' /home/fchevalier/tmp/linux-2.6.18-mh4/include/net/bluetooth/hci.h /home/fchevalier/tmp/linux-2.6.18-mh4-fch/include/net/bluetooth/hci.h --- /home/fchevalier/tmp/linux-2.6.18-mh4/include/net/bluetooth/hci.h 2006-10-12 11:30:35.000000000 +0200 +++ /home/fchevalier/tmp/linux-2.6.18-mh4-fch/include/net/bluetooth/hci.h 2006-10-12 12:39:22.000000000 +0200 @@ -764,12 +764,15 @@ __u32 sco_tx; __u32 sco_rx; __u32 byte_rx; __u32 byte_tx; }; +/* Fields down there are mostly the same as hci_dev, + as this structure is meant to communicate info + to userspace */ struct hci_dev_info { __u16 dev_id; char name[8]; bdaddr_t bdaddr; @@ -779,15 +782,19 @@ __u8 features[8]; __u32 pkt_type; __u32 link_policy; __u32 link_mode; + /* Maximum transmition unit for ACL packets */ __u16 acl_mtu; + /* Number of ACL packets the baseband is able to buffer */ __u16 acl_pkts; + /* Maximum transmition unit for SCO packets */ __u16 sco_mtu; + /* Number of SCO packets the baseband is able to buffer */ __u16 sco_pkts; struct hci_dev_stats stat; }; struct hci_conn_info { diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='sco.*' /home/fchevalier/tmp/linux-2.6.18-mh4/include/net/bluetooth/hci_core.h /home/fchevalier/tmp/linux-2.6.18-mh4-fch/include/net/bluetooth/hci_core.h --- /home/fchevalier/tmp/linux-2.6.18-mh4/include/net/bluetooth/hci_core.h 2006-10-12 11:30:35.000000000 +0200 +++ /home/fchevalier/tmp/linux-2.6.18-mh4-fch/include/net/bluetooth/hci_core.h 2006-10-12 17:45:39.000000000 +0200 @@ -22,12 +22,14 @@ SOFTWARE IS DISCLAIMED. */ #ifndef __HCI_CORE_H #define __HCI_CORE_H +#include + #include /* HCI upper protocols */ #define HCI_PROTO_L2CAP 0 #define HCI_PROTO_SCO 1 @@ -85,18 +87,24 @@ __u16 sniff_min_interval; __u16 sniff_max_interval; unsigned long quirks; atomic_t cmd_cnt; + /* Number of available controller buffers for ACL packets */ unsigned int acl_cnt; - unsigned int sco_cnt; + /* Number of available controller buffers for SCO packets */ + atomic_t sco_cnt; + /* Maximum transmition unit for ACL packets */ unsigned int acl_mtu; + /* Maximum transmition unit for SCO packets */ unsigned int sco_mtu; + /* Maximum number of ACL packets the controller is able to buffer */ unsigned int acl_pkts; + /* Maximum number of SCO packets the controller is able to buffer */ unsigned int sco_pkts; unsigned long cmd_last_tx; unsigned long acl_last_tx; unsigned long sco_last_tx; @@ -142,41 +150,51 @@ }; struct hci_conn { struct list_head list; atomic_t refcnt; - spinlock_t lock; bdaddr_t dst; __u16 handle; __u16 state; __u8 mode; + /* type : ACL or SCO */ __u8 type; __u8 out; __u8 dev_class[3]; __u8 features[8]; __u16 interval; __u16 link_policy; __u32 link_mode; __u8 power_save; unsigned long pend; - unsigned int sent; - - struct sk_buff_head data_q; - + /* sent represents the number of packets this connections + has "on the wire" : .... oh f.... there are no wire + with bluetooth. By on the wire, i mean packets that have been sent + to the HCI device, and that are still in its buffers */ + atomic_t sent; + + struct sk_buff_head out_q; + /* This is only used for SCO for now */ + void (*pkt_sent_cb)(struct hci_conn *conn); + + /* tx timer : used only for SCO */ + struct hrtimer tx_timer; + /* Disconnect timer */ struct timer_list disc_timer; struct timer_list idle_timer; struct work_struct work; struct device dev; struct hci_dev *hdev; void *l2cap_data; + /* private use for sco */ void *sco_data; void *priv; struct hci_conn *link; }; @@ -610,13 +628,16 @@ int hci_register_notifier(struct notifier_block *nb); int hci_unregister_notifier(struct notifier_block *nb); int hci_send_cmd(struct hci_dev *hdev, __u16 ogf, __u16 ocf, __u32 plen, void *param); int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags); -int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb); +int hci_send_sco(struct hci_conn *conn, + struct sk_buff *skb, + int sndbufsize, + void (*send_complete_cb)(struct hci_conn *conn)); void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 ogf, __u16 ocf); void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data); /* ----- HCI Sockets ----- */ diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='sco.*' /home/fchevalier/tmp/linux-2.6.18-mh4/net/bluetooth/hci_conn.c /home/fchevalier/tmp/linux-2.6.18-mh4-fch/net/bluetooth/hci_conn.c --- /home/fchevalier/tmp/linux-2.6.18-mh4/net/bluetooth/hci_conn.c 2006-10-12 11:30:35.000000000 +0200 +++ /home/fchevalier/tmp/linux-2.6.18-mh4-fch/net/bluetooth/hci_conn.c 2006-10-12 17:31:39.000000000 +0200 @@ -179,23 +179,27 @@ conn->type = type; conn->mode = HCI_CM_ACTIVE; conn->state = BT_OPEN; conn->power_save = 1; - skb_queue_head_init(&conn->data_q); + conn->pkt_sent_cb = NULL; + skb_queue_head_init(&conn->out_q); + + hrtimer_init(&conn->tx_timer, CLOCK_MONOTONIC, HRTIMER_NORESTART); init_timer(&conn->disc_timer); conn->disc_timer.function = hci_conn_timeout; conn->disc_timer.data = (unsigned long) conn; init_timer(&conn->idle_timer); conn->idle_timer.function = hci_conn_idle; conn->idle_timer.data = (unsigned long) conn; atomic_set(&conn->refcnt, 0); + atomic_set(&conn->sent, 0); hci_dev_hold(hdev); tasklet_disable(&hdev->tx_task); hci_conn_hash_add(hdev, conn); @@ -216,38 +220,40 @@ BT_DBG("%s conn %p handle %d", hdev->name, conn, conn->handle); del_timer(&conn->idle_timer); del_timer(&conn->disc_timer); + hrtimer_cancel(&conn->tx_timer); + if (conn->type == SCO_LINK) { struct hci_conn *acl = conn->link; if (acl) { acl->link = NULL; hci_conn_put(acl); } } else { struct hci_conn *sco = conn->link; if (sco) sco->link = NULL; /* Unacked frames */ - hdev->acl_cnt += conn->sent; + hdev->acl_cnt += atomic_read(&conn->sent); } tasklet_disable(&hdev->tx_task); hci_conn_del_sysfs(conn); hci_conn_hash_del(hdev, conn); if (hdev->notify) hdev->notify(hdev, HCI_NOTIFY_CONN_DEL); tasklet_enable(&hdev->tx_task); - skb_queue_purge(&conn->data_q); + skb_queue_purge(&conn->out_q); hci_dev_put(hdev); /* will free via device release */ put_device(&conn->dev); diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='sco.*' /home/fchevalier/tmp/linux-2.6.18-mh4/net/bluetooth/hci_core.c /home/fchevalier/tmp/linux-2.6.18-mh4-fch/net/bluetooth/hci_core.c --- /home/fchevalier/tmp/linux-2.6.18-mh4/net/bluetooth/hci_core.c 2006-10-12 11:30:35.000000000 +0200 +++ /home/fchevalier/tmp/linux-2.6.18-mh4-fch/net/bluetooth/hci_core.c 2006-10-12 19:01:26.000000000 +0200 @@ -616,14 +616,15 @@ hci_conn_hash_flush(hdev); hci_dev_unlock_bh(hdev); if (hdev->flush) hdev->flush(hdev); - atomic_set(&hdev->cmd_cnt, 1); - hdev->acl_cnt = 0; hdev->sco_cnt = 0; + atomic_set(&hdev->cmd_cnt, 1); + atomic_set(&hdev->sco_cnt, 0); + hdev->acl_cnt = 0; if (!test_bit(HCI_RAW, &hdev->flags)) ret = __hci_request(hdev, hci_reset_req, 0, msecs_to_jiffies(HCI_INIT_TIMEOUT)); done: @@ -1093,100 +1094,110 @@ hci_add_acl_hdr(skb, conn->handle, flags | ACL_START); if (!(list = skb_shinfo(skb)->frag_list)) { /* Non fragmented */ BT_DBG("%s nonfrag skb %p len %d", hdev->name, skb, skb->len); - skb_queue_tail(&conn->data_q, skb); + skb_queue_tail(&conn->out_q, skb); } else { /* Fragmented */ BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len); skb_shinfo(skb)->frag_list = NULL; /* Queue all fragments atomically */ - spin_lock_bh(&conn->data_q.lock); + spin_lock_bh(&conn->out_q.lock); - __skb_queue_tail(&conn->data_q, skb); + __skb_queue_tail(&conn->out_q, skb); do { skb = list; list = list->next; skb->dev = (void *) hdev; bt_cb(skb)->pkt_type = HCI_ACLDATA_PKT; hci_add_acl_hdr(skb, conn->handle, flags | ACL_CONT); BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len); - __skb_queue_tail(&conn->data_q, skb); + __skb_queue_tail(&conn->out_q, skb); } while (list); - spin_unlock_bh(&conn->data_q.lock); + spin_unlock_bh(&conn->out_q.lock); } hci_sched_tx(hdev); return 0; } EXPORT_SYMBOL(hci_send_acl); -/* Send SCO data */ -int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb) +static inline void __hci_send_sco(struct hci_conn *conn, struct sk_buff *skb) { - struct hci_dev *hdev = conn->hdev; struct hci_sco_hdr hdr; - BT_DBG("%s len %d", hdev->name, skb->len); - - if (skb->len > hdev->sco_mtu) { - kfree_skb(skb); - return -EINVAL; - } - + BT_DBG("skb %p len %d", skb, skb->len); + /* preparing frame */ hdr.handle = __cpu_to_le16(conn->handle); hdr.dlen = skb->len; skb->h.raw = skb_push(skb, HCI_SCO_HDR_SIZE); memcpy(skb->h.raw, &hdr, HCI_SCO_HDR_SIZE); - skb->dev = (void *) hdev; + skb->dev = (void *) conn->hdev; bt_cb(skb)->pkt_type = HCI_SCODATA_PKT; - skb_queue_tail(&conn->data_q, skb); - hci_sched_tx(hdev); - return 0; + + skb_queue_tail(&conn->out_q, skb); + + hci_sched_tx(conn->hdev); +} + +int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb, + int sndbufsize, void (*send_complete_cb)(struct hci_conn *conn)) +{ + BT_DBG("conn %p skb %p sndbufsize %d", conn, skb, sndbufsize); + if(skb_queue_len(&conn->out_q) < sndbufsize) { + __hci_send_sco(conn, skb); + conn->pkt_sent_cb = send_complete_cb; + return 0; + } + else { + return -EAGAIN; + } } EXPORT_SYMBOL(hci_send_sco); /* ---- HCI TX task (outgoing data) ---- */ -/* HCI Connection scheduler */ -static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int *quote) +/* HCI ACL Connection scheduler */ +static inline struct hci_conn *hci_low_sent_acl(struct hci_dev *hdev, int *quote) { struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *conn = NULL; - int num = 0, min = ~0; + unsigned int num = 0, min = ~0; struct list_head *p; /* We don't have to lock device here. Connections are always * added and removed with TX task disabled. */ list_for_each(p, &h->list) { struct hci_conn *c; c = list_entry(p, struct hci_conn, list); - if (c->type != type || c->state != BT_CONNECTED - || skb_queue_empty(&c->data_q)) + BT_DBG("c->type %d c->state %d len(c->out_q) %d min %d c->sent %d", + c->type, c->state, skb_queue_len(&c->out_q), min, atomic_read(&c->sent)); + + if (c->type != ACL_LINK || c->state != BT_CONNECTED + || skb_queue_empty(&c->out_q)) continue; num++; - if (c->sent < min) { - min = c->sent; + if (atomic_read(&c->sent) < min) { + min = atomic_read(&c->sent); conn = c; } } if (conn) { - int cnt = (type == ACL_LINK ? hdev->acl_cnt : hdev->sco_cnt); - int q = cnt / num; + int q = hdev->acl_cnt / num; *quote = q ? q : 1; } else *quote = 0; BT_DBG("conn %p quote %d", conn, *quote); return conn; @@ -1200,13 +1211,13 @@ BT_ERR("%s ACL tx timeout", hdev->name); /* Kill stalled connections */ list_for_each(p, &h->list) { c = list_entry(p, struct hci_conn, list); - if (c->type == ACL_LINK && c->sent) { + if (c->type == ACL_LINK && atomic_read(&c->sent)) { BT_ERR("%s killing stalled ACL connection %s", hdev->name, batostr(&c->dst)); hci_acl_disconn(c, 0x13); } } } @@ -1223,63 +1234,122 @@ /* ACL tx timeout must be longer than maximum * link supervision timeout (40.9 seconds) */ if (!hdev->acl_cnt && (jiffies - hdev->acl_last_tx) > (HZ * 45)) hci_acl_tx_to(hdev); } - while (hdev->acl_cnt && (conn = hci_low_sent(hdev, ACL_LINK, "e))) { - while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + while (hdev->acl_cnt && (conn = hci_low_sent_acl(hdev, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->out_q))) { BT_DBG("skb %p len %d", skb, skb->len); hci_conn_enter_active_mode(conn); hci_send_frame(skb); hdev->acl_last_tx = jiffies; hdev->acl_cnt--; - conn->sent++; + atomic_inc(&conn->sent); } } } -/* Schedule SCO */ -static inline void hci_sched_sco(struct hci_dev *hdev) +/* HCI SCO tx timer */ + +static int hci_sco_tx_timer(struct hrtimer *timer) { - struct hci_conn *conn; - struct sk_buff *skb; - int quote; + struct hci_conn *conn = container_of(timer, struct hci_conn, tx_timer); +#ifdef CONFIG_BT_HCI_CORE_DEBUG + ktime_t now = timer->base->get_time(); +#endif - BT_DBG("%s", hdev->name); + BT_DBG("%s, conn %p, time %5lu.%06lu", conn->hdev->name, conn, + (unsigned long) now.tv64, + do_div(now.tv64, NSEC_PER_SEC) / 1000); - while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, "e))) { - while (quote-- && (skb = skb_dequeue(&conn->data_q))) { - BT_DBG("skb %p len %d", skb, skb->len); - hci_send_frame(skb); + if(atomic_read(&conn->sent) > 0) { + atomic_dec(&conn->sent); + atomic_inc(&conn->hdev->sco_cnt); + hci_sched_tx(conn->hdev); + } + /* Wake up writers */ + if(conn->pkt_sent_cb) { + conn->pkt_sent_cb(conn); + } + return HRTIMER_NORESTART; +} + +/* HCI SCO Connection scheduler */ - conn->sent++; - if (conn->sent == ~0) - conn->sent = 0; +static inline void hci_sched_sco(struct hci_dev *hdev) +{ + struct hci_conn_hash *h = &hdev->conn_hash; + struct sk_buff *skb; + struct list_head *p; + struct hci_conn *c; + + BT_DBG("%s", hdev->name); + + /* We don't have to lock device here. Connections are always + * added and removed with TX task disabled. */ + list_for_each(p, &h->list) { + c = list_entry(p, struct hci_conn, list); + + /* SCO scheduling algorithm makes sure there is never more than + 1 outstanding packet for each connection */ + if (c->type == SCO_LINK && atomic_read(&c->sent) < 1 && c->state == BT_CONNECTED) + { + if(atomic_read(&hdev->sco_cnt) > 0) { + if((skb = skb_dequeue(&c->out_q)) != NULL) { + ktime_t now, pkt_time; + + hci_send_frame(skb); + + atomic_inc(&c->sent); + atomic_dec(&hdev->sco_cnt); + + c->tx_timer.function = hci_sco_tx_timer; + + pkt_time = + ktime_set(0, NSEC_PER_SEC / 16000 * (skb->len - HCI_SCO_HDR_SIZE)); + now = c->tx_timer.base->get_time(); + + if(c->tx_timer.expires.tv64 == 0) { + c->tx_timer.expires = now; + } + + c->tx_timer.expires.tv64 += pkt_time.tv64; + if(c->tx_timer.expires.tv64 > now.tv64) { + hrtimer_restart(&c->tx_timer); + } + else { + /* Timer is to expire in the past - this can happen if timer base + precision is less than pkt_time. In this case we force timer + expiration by calling its expires function */ + c->tx_timer.function(&c->tx_timer); + } + } + } } } } static void hci_tx_task(unsigned long arg) { struct hci_dev *hdev = (struct hci_dev *) arg; struct sk_buff *skb; 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", hdev->name, hdev->acl_cnt, atomic_read(&hdev->sco_cnt)); /* Schedule queues and send stuff to HCI driver */ - hci_sched_acl(hdev); - hci_sched_sco(hdev); + hci_sched_acl(hdev); + /* Send next queued raw (unknown type) packet */ while ((skb = skb_dequeue(&hdev->raw_q))) hci_send_frame(skb); read_unlock(&hci_task_lock); } diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='sco.*' /home/fchevalier/tmp/linux-2.6.18-mh4/net/bluetooth/hci_event.c /home/fchevalier/tmp/linux-2.6.18-mh4-fch/net/bluetooth/hci_event.c --- /home/fchevalier/tmp/linux-2.6.18-mh4/net/bluetooth/hci_event.c 2006-10-12 11:30:35.000000000 +0200 +++ /home/fchevalier/tmp/linux-2.6.18-mh4-fch/net/bluetooth/hci_event.c 2006-10-12 17:37:07.000000000 +0200 @@ -307,13 +307,13 @@ switch (ocf) { case OCF_READ_LOCAL_VERSION: lv = (struct hci_rp_read_loc_version *) skb->data; if (lv->status) { - BT_DBG("%s READ_LOCAL_VERSION failed %d", hdev->name, lf->status); + BT_DBG("%s READ_LOCAL_VERSION failed %d", hdev->name, lv->status); break; } hdev->hci_ver = lv->hci_ver; hdev->hci_rev = btohs(lv->hci_rev); hdev->manufacturer = btohs(lv->manufacturer); @@ -369,13 +369,13 @@ if (test_bit(HCI_QUIRK_FIXUP_BUFFER_SIZE, &hdev->quirks)) { hdev->sco_mtu = 64; hdev->sco_pkts = 8; } hdev->acl_cnt = hdev->acl_pkts; - hdev->sco_cnt = hdev->sco_pkts; + atomic_set(&hdev->sco_cnt, hdev->sco_pkts); BT_DBG("%s mtu: acl %d, sco %d max_pkt: acl %d, sco %d", hdev->name, hdev->acl_mtu, hdev->sco_mtu, hdev->acl_pkts, hdev->sco_pkts); break; case OCF_READ_BD_ADDR: @@ -858,21 +858,21 @@ handle = __le16_to_cpu(get_unaligned(ptr++)); count = __le16_to_cpu(get_unaligned(ptr++)); conn = hci_conn_hash_lookup_handle(hdev, handle); if (conn) { - conn->sent -= count; + atomic_sub(count, &conn->sent); - if (conn->type == SCO_LINK) { - if ((hdev->sco_cnt += count) > hdev->sco_pkts) - hdev->sco_cnt = hdev->sco_pkts; - } else { + if (conn->type == ACL_LINK) { if ((hdev->acl_cnt += count) > hdev->acl_pkts) hdev->acl_cnt = hdev->acl_pkts; } + /* Note : we do not use the "number of completed packets" event + to increment hdev->sco_cnt, as this feature is only optionnally support + by bluetooth controllers. So there is no if branch for SCO_LINK packets */ } } hci_sched_tx(hdev); tasklet_enable(&hdev->tx_task); }