From: Fabien Chevalier <fabchevalier@free.fr>
To: BlueZ development <bluez-devel@lists.sourceforge.net>
Subject: Re: [Bluez-devel] [PATCH] SCO flow control
Date: Tue, 30 May 2006 15:28:02 +0200 [thread overview]
Message-ID: <447C4862.5050205@free.fr> (raw)
In-Reply-To: <1148934839.31689.116.camel@localhost>
[-- Attachment #1: Type: text/plain, Size: 1446 bytes --]
Hello Marcel&All,
> please split the changes to Bluetooth core and SCO module. Your changes
> to the core will conflict with the automatic sniff mode support and I
> need to sort that out.
Ok, i've just done that, i've split the patch into three smaller bits.
- Patch part 1 are changes to hci core required for SCO flow control
itself.
- Patch part 2 are the changes to the SCO socket layer.
- Patch part 3 are additionnal comments on hci core code.
Please note however that part 1 and part 2 can not be applied seperately.
>
> And for the core part, please be brief with any additional comments. My
> rule for comments is to put them where the code is not clear by itself.
I would tend to agree with the general philosophy of the thing, however
for my point of view bluez kernel code lacks proper fields structure
explanation. Comparing for skbuff.h & usb.h to hci_core.h for instance
is just night and day... skbuff.h & usb.h are just well commented files,
while hci.h and hci_core.h have hardly any comment. :-(
> What you have done is actually over-commenting and that makes the code
> harder to read and understand.
Could you give me an example ? I don't see which part is not lined up
with kernel coding style :-(
anyway,
All this is really *cosmetic* compared to the feature itself. :-)
On another level, does anyone here have anything to add on the feature
itself, or the way i designed it ??
Cheers,
Fabien
[-- Attachment #2: bluez-sco-flowcontrol-1of3.diff --]
[-- Type: text/plain, Size: 13176 bytes --]
diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='sco.*' /home/fchevalier/tmp/linux-2.6.16.16-orig/include/net/bluetooth/hci_core.h /home/fchevalier/tmp/linux-2.6.16.16/include/net/bluetooth/hci_core.h
--- /home/fchevalier/tmp/linux-2.6.16.16-orig/include/net/bluetooth/hci_core.h 2006-05-11 03:56:24.000000000 +0200
+++ /home/fchevalier/tmp/linux-2.6.16.16/include/net/bluetooth/hci_core.h 2006-05-25 17:47:11.000000000 +0200
@@ -142,26 +148,39 @@
atomic_t refcnt;
spinlock_t lock;
bdaddr_t dst;
__u16 handle;
__u16 state;
+ /* type : ACL or SCO */
__u8 type;
__u8 out;
__u8 dev_class[3];
__u32 link_mode;
unsigned long pend;
+ /* 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 */
unsigned int sent;
+ /* Queued packets for this connection
+ Starting with bluez core version 2.9, SCO packets are not in this queue anymore,
+ but remain in the sockets send queue until they are forwared to the driver by
+ the hci scheduler */
struct sk_buff_head data_q;
- struct timer_list timer;
+ /* Data timer : used only for SCO */
+ struct timer_list data_timer;
+ /* Disconnect timer */
+ struct timer_list disc_timer;
struct hci_dev *hdev;
void *l2cap_data;
+ /* private use for sco */
void *sco_data;
void *priv;
struct hci_conn *link;
};
@@ -283,37 +302,37 @@
struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *src);
int hci_conn_auth(struct hci_conn *conn);
int hci_conn_encrypt(struct hci_conn *conn);
int hci_conn_change_link_key(struct hci_conn *conn);
int hci_conn_switch_role(struct hci_conn *conn, uint8_t role);
-static inline void hci_conn_set_timer(struct hci_conn *conn, unsigned long timeout)
+static inline void hci_conn_set_disc_timer(struct hci_conn *conn, unsigned long timeout)
{
- mod_timer(&conn->timer, jiffies + timeout);
+ mod_timer(&conn->disc_timer, jiffies + timeout);
}
-static inline void hci_conn_del_timer(struct hci_conn *conn)
+static inline void hci_conn_del_disc_timer(struct hci_conn *conn)
{
- del_timer(&conn->timer);
+ del_timer(&conn->disc_timer);
}
static inline void hci_conn_hold(struct hci_conn *conn)
{
atomic_inc(&conn->refcnt);
- hci_conn_del_timer(conn);
+ hci_conn_del_disc_timer(conn);
}
static inline void hci_conn_put(struct hci_conn *conn)
{
if (atomic_dec_and_test(&conn->refcnt)) {
if (conn->type == ACL_LINK) {
unsigned long timeo = (conn->out) ?
HCI_DISCONN_TIMEOUT : HCI_DISCONN_TIMEOUT * 2;
- hci_conn_set_timer(conn, timeo);
+ hci_conn_set_disc_timer(conn, timeo);
} else
- hci_conn_set_timer(conn, HZ / 100);
+ hci_conn_set_disc_timer(conn, HZ / 100);
}
}
/* ----- HCI tasks ----- */
static inline void hci_sched_cmd(struct hci_dev *hdev)
{
@@ -419,19 +438,20 @@
char *name;
unsigned int id;
unsigned long flags;
void *priv;
- int (*connect_ind) (struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type);
- int (*connect_cfm) (struct hci_conn *conn, __u8 status);
- int (*disconn_ind) (struct hci_conn *conn, __u8 reason);
- int (*recv_acldata) (struct hci_conn *conn, struct sk_buff *skb, __u16 flags);
- int (*recv_scodata) (struct hci_conn *conn, struct sk_buff *skb);
- int (*auth_cfm) (struct hci_conn *conn, __u8 status);
- int (*encrypt_cfm) (struct hci_conn *conn, __u8 status);
+ int (*connect_ind) (struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type);
+ int (*connect_cfm) (struct hci_conn *conn, __u8 status);
+ int (*disconn_ind) (struct hci_conn *conn, __u8 reason);
+ int (*recv_acldata) (struct hci_conn *conn, struct sk_buff *skb, __u16 flags);
+ int (*recv_scodata) (struct hci_conn *conn, struct sk_buff *skb);
+ struct sk_buff* (*get_scodata) (struct hci_conn *conn);
+ int (*auth_cfm) (struct hci_conn *conn, __u8 status);
+ int (*encrypt_cfm) (struct hci_conn *conn, __u8 status);
};
static inline int hci_proto_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type)
{
register struct hci_proto *hp;
int mask = 0;
@@ -575,13 +595,13 @@
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);
+void hci_stream_sco(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.16.16-orig/net/bluetooth/af_bluetooth.c /home/fchevalier/tmp/linux-2.6.16.16/net/bluetooth/af_bluetooth.c
--- /home/fchevalier/tmp/linux-2.6.16.16-orig/net/bluetooth/af_bluetooth.c 2006-05-11 03:56:24.000000000 +0200
+++ /home/fchevalier/tmp/linux-2.6.16.16/net/bluetooth/af_bluetooth.c 2006-05-21 17:38:28.000000000 +0200
@@ -46,13 +46,13 @@
#ifndef CONFIG_BT_SOCK_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
-#define VERSION "2.8"
+#define VERSION "2.9"
/* Bluetooth sockets */
#define BT_MAX_PROTO 8
static struct net_proto_family *bt_proto[BT_MAX_PROTO];
int bt_sock_register(int proto, struct net_proto_family *ops)
diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='sco.*' /home/fchevalier/tmp/linux-2.6.16.16-orig/net/bluetooth/hci_conn.c /home/fchevalier/tmp/linux-2.6.16.16/net/bluetooth/hci_conn.c
--- /home/fchevalier/tmp/linux-2.6.16.16-orig/net/bluetooth/hci_conn.c 2006-05-11 03:56:24.000000000 +0200
+++ /home/fchevalier/tmp/linux-2.6.16.16/net/bluetooth/hci_conn.c 2006-05-25 18:09:19.000000000 +0200
@@ -130,17 +130,17 @@
else
conn->state = BT_CLOSED;
hci_dev_unlock(hdev);
return;
}
-static void hci_conn_init_timer(struct hci_conn *conn)
+static void hci_conn_init_disc_timer(struct hci_conn *conn)
{
- init_timer(&conn->timer);
- conn->timer.function = hci_conn_timeout;
- conn->timer.data = (unsigned long)conn;
+ init_timer(&conn->disc_timer);
+ conn->disc_timer.function = hci_conn_timeout;
+ conn->disc_timer.data = (unsigned long)conn;
}
struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst)
{
struct hci_conn *conn;
@@ -153,13 +153,14 @@
bacpy(&conn->dst, dst);
conn->type = type;
conn->hdev = hdev;
conn->state = BT_OPEN;
skb_queue_head_init(&conn->data_q);
- hci_conn_init_timer(conn);
+ hci_conn_init_disc_timer(conn);
+ init_timer(&conn->data_timer);
atomic_set(&conn->refcnt, 0);
hci_dev_hold(hdev);
tasklet_disable(&hdev->tx_task);
@@ -176,13 +177,14 @@
int hci_conn_del(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
BT_DBG("%s conn %p handle %d", hdev->name, conn, conn->handle);
- hci_conn_del_timer(conn);
+ hci_conn_del_disc_timer(conn);
+ del_timer_sync(&conn->data_timer);
if (conn->type == SCO_LINK) {
struct hci_conn *acl = conn->link;
if (acl) {
acl->link = NULL;
hci_conn_put(acl);
diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='sco.*' /home/fchevalier/tmp/linux-2.6.16.16-orig/net/bluetooth/hci_core.c /home/fchevalier/tmp/linux-2.6.16.16/net/bluetooth/hci_core.c
--- /home/fchevalier/tmp/linux-2.6.16.16-orig/net/bluetooth/hci_core.c 2006-05-11 03:56:24.000000000 +0200
+++ /home/fchevalier/tmp/linux-2.6.16.16/net/bluetooth/hci_core.c 2006-05-30 12:56:00.000000000 +0200
@@ -1116,69 +1116,49 @@
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)
+/* Push sco data if not already in progress */
+void hci_stream_sco(struct hci_conn *conn)
{
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;
- }
-
- 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;
- bt_cb(skb)->pkt_type = HCI_SCODATA_PKT;
- skb_queue_tail(&conn->data_q, skb);
hci_sched_tx(hdev);
- return 0;
}
-EXPORT_SYMBOL(hci_send_sco);
+EXPORT_SYMBOL(hci_stream_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;
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
+ if (c->type != ACL_LINK || c->state != BT_CONNECTED
|| skb_queue_empty(&c->data_q))
continue;
num++;
if (c->sent < min) {
min = 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;
@@ -1215,41 +1195,95 @@
/* 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 (hdev->acl_cnt && (conn = hci_low_sent_acl(hdev, "e))) {
while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
BT_DBG("skb %p len %d", skb, skb->len);
hci_send_frame(skb);
hdev->acl_last_tx = jiffies;
hdev->acl_cnt--;
conn->sent++;
}
}
}
-/* Schedule SCO */
+/* HCI SCO Connection scheduler */
+
+static void hci_send_sco(struct hci_dev *hdev, struct hci_conn *conn, struct sk_buff *skb)
+{
+ struct hci_sco_hdr hdr;
+
+ 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;
+ bt_cb(skb)->pkt_type = HCI_SCODATA_PKT;
+
+ hci_send_frame(skb);
+}
+
+static void hci_sco_tx_timer(unsigned long data)
+{
+ struct hci_conn *conn = (struct hci_conn *) data;
+ BT_DBG("%s, conn %p", conn->hdev->name, conn);
+ if(conn->sent > 0) {
+ conn->sent--;
+ conn->hdev->sco_cnt++;
+ hci_sched_tx(conn->hdev);
+ }
+}
+
static inline void hci_sched_sco(struct hci_dev *hdev)
{
- struct hci_conn *conn;
+ struct hci_conn_hash *h = &hdev->conn_hash;
struct sk_buff *skb;
- int quote;
-
+ struct hci_proto *hp = hci_proto[HCI_PROTO_SCO];
+ struct list_head *p;
+ struct hci_conn *c;
+
BT_DBG("%s", hdev->name);
- 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);
+ /* 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);
- conn->sent++;
- if (conn->sent == ~0)
- conn->sent = 0;
+ /* SCO scheduling algorithm makes sure there is never more than
+ 1 outstanding packet for each connection -- I tried with
+ 2 and more, however there voice quality was not significantly
+ increased. So let it be 1.*/
+ if (c->sent < 1 && c->type == SCO_LINK && c->state == BT_CONNECTED)
+ {
+ if(hdev->sco_cnt > 0) {
+ if((skb = hp->get_scodata(c)) != NULL) {
+ hci_send_sco(hdev, c, skb);
+
+ c->sent++;
+ hdev->sco_cnt--;
+
+ c->data_timer.data = (unsigned long)c;
+ c->data_timer.function = hci_sco_tx_timer;
+ /* At this point we are already supposed to have skb->len a multiple of 16 --
+ this is supposed to be enforced by SCO socket layer */
+ mod_timer(&c->data_timer, jiffies + 1000 * (skb->len % 16) / HZ );
+ }
+ }
+ else {
+ /* exit from loop, as there is no free HCI SCO buffer */
+ break;
+ }
}
}
}
static void hci_tx_task(unsigned long arg)
{
@@ -1259,16 +1293,16 @@
read_lock(&hci_task_lock);
BT_DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, 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);
}
[-- Attachment #3: bluez-sco-flowcontrol-2of3.diff --]
[-- Type: text/plain, Size: 14042 bytes --]
diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='*hci*' --exclude='af_bluetooth.*' /home/fchevalier/tmp/linux-2.6.16.16-orig/include/net/bluetooth/sco.h /home/fchevalier/tmp/linux-2.6.16.16/include/net/bluetooth/sco.h
--- /home/fchevalier/tmp/linux-2.6.16.16-orig/include/net/bluetooth/sco.h 2006-05-11 03:56:24.000000000 +0200
+++ /home/fchevalier/tmp/linux-2.6.16.16/include/net/bluetooth/sco.h 2006-05-28 17:36:49.000000000 +0200
@@ -23,18 +23,13 @@
*/
#ifndef __SCO_H
#define __SCO_H
/* SCO defaults */
-#define SCO_DEFAULT_MTU 500
-#define SCO_DEFAULT_FLUSH_TO 0xFFFF
-
#define SCO_CONN_TIMEOUT (HZ * 40)
-#define SCO_DISCONN_TIMEOUT (HZ * 2)
-#define SCO_CONN_IDLE_TIMEOUT (HZ * 60)
/* SCO socket address */
struct sockaddr_sco {
sa_family_t sco_family;
bdaddr_t sco_bdaddr;
};
@@ -48,12 +43,15 @@
#define SCO_CONNINFO 0x02
struct sco_conninfo {
__u16 hci_handle;
__u8 dev_class[3];
};
+#define SCO_TXBUFS 0x03
+#define SCO_RXBUFS 0x04
+
/* ---- SCO connections ---- */
struct sco_conn {
struct hci_conn *hcon;
bdaddr_t *dst;
bdaddr_t *src;
diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='*hci*' --exclude='af_bluetooth.*' /home/fchevalier/tmp/linux-2.6.16.16-orig/net/bluetooth/sco.c /home/fchevalier/tmp/linux-2.6.16.16/net/bluetooth/sco.c
--- /home/fchevalier/tmp/linux-2.6.16.16-orig/net/bluetooth/sco.c 2006-05-11 03:56:24.000000000 +0200
+++ /home/fchevalier/tmp/linux-2.6.16.16/net/bluetooth/sco.c 2006-05-30 13:58:51.000000000 +0200
@@ -51,28 +51,48 @@
#ifndef CONFIG_BT_SCO_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
-#define VERSION "0.5"
+#define VERSION "0.6"
+
+#define MAX_SCO_TXBUFS 200
+#define MAX_SCO_RXBUFS 200
+
+#define DEFAULT_SCO_TXBUFS 5
+#define DEFAULT_SCO_RXBUFS 5
+
+static unsigned int tx_quality_tune;
+static unsigned int rx_quality_tune;
static const struct proto_ops sco_sock_ops;
static struct bt_sock_list sco_sk_list = {
.lock = RW_LOCK_UNLOCKED
};
+/* Local functions declaration */
+
static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent);
static void sco_chan_del(struct sock *sk, int err);
static int sco_conn_del(struct hci_conn *conn, int err);
static void sco_sock_close(struct sock *sk);
static void sco_sock_kill(struct sock *sk);
+static void sco_sock_wfree(struct sk_buff *skb);
+static void sco_sock_rfree(struct sk_buff *skb);
+static long sock_wait_for_wmem(struct sock * sk, long timeo);
+
+static struct sk_buff *sco_skb_send_alloc(struct sock *sk, unsigned long len,
+ int nb, int *errcode);
+
+static int sco_sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb);
+
/* ---- SCO timers ---- */
static void sco_sock_timeout(unsigned long arg)
{
struct sock *sk = (struct sock *) arg;
BT_DBG("sock %p state %d", sk, sk->sk_state);
@@ -230,58 +250,30 @@
done:
hci_dev_unlock_bh(hdev);
hci_dev_put(hdev);
return err;
}
-static inline int sco_send_frame(struct sock *sk, struct msghdr *msg, int len)
-{
- struct sco_conn *conn = sco_pi(sk)->conn;
- struct sk_buff *skb;
- int err, count;
-
- /* Check outgoing MTU */
- if (len > conn->mtu)
- return -EINVAL;
-
- BT_DBG("sk %p len %d", sk, len);
-
- count = min_t(unsigned int, conn->mtu, len);
- if (!(skb = bt_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err)))
- return err;
-
- if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) {
- err = -EFAULT;
- goto fail;
- }
-
- if ((err = hci_send_sco(conn->hcon, skb)) < 0)
- goto fail;
-
- return count;
-
-fail:
- kfree_skb(skb);
- return err;
-}
-
static inline void sco_recv_frame(struct sco_conn *conn, struct sk_buff *skb)
{
struct sock *sk = sco_chan_get(conn);
if (!sk)
goto drop;
BT_DBG("sk %p len %d", sk, skb->len);
if (sk->sk_state != BT_CONNECTED)
goto drop;
- if (!sock_queue_rcv_skb(sk, skb))
+ if (sco_sock_queue_rcv_skb(sk, skb) == 0) {
return;
-
+ }
+ else if(rx_quality_tune) {
+ printk(KERN_INFO "rx_quality_tune: audio cut !\n");
+ }
drop:
kfree_skb(skb);
return;
}
/* -------- Socket interface ---------- */
@@ -328,13 +320,30 @@
static void sco_sock_destruct(struct sock *sk)
{
BT_DBG("sk %p", sk);
skb_queue_purge(&sk->sk_receive_queue);
- skb_queue_purge(&sk->sk_write_queue);
+}
+
+static void sco_sock_write_space(struct sock *sk)
+{
+ read_lock(&sk->sk_callback_lock);
+
+ /* Unlock writers ASAP
+ */
+ if((atomic_read(&sk->sk_wmem_alloc)) < sk->sk_sndbuf) {
+ if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
+ wake_up_interruptible(sk->sk_sleep);
+
+ /* Should agree with poll, otherwise some programs break */
+ if (sock_writeable(sk))
+ sk_wake_async(sk, 2, POLL_OUT);
+ }
+
+ read_unlock(&sk->sk_callback_lock);
}
static void sco_sock_cleanup_listen(struct sock *parent)
{
struct sock *sk;
@@ -360,12 +369,19 @@
BT_DBG("sk %p state %d", sk, sk->sk_state);
/* Kill poor orphan */
bt_sock_unlink(&sco_sk_list, sk);
sock_set_flag(sk, SOCK_DEAD);
+
+ /* We cannot purge write queue in socket destructor as
+ it was done before, as skb in this queue hold a reference
+ to the socket itself */
+ skb_queue_purge(&sk->sk_write_queue);
+
+ /* release socket */
sock_put(sk);
}
/* Close socket.
* Must be called on unlocked socket.
*/
@@ -376,13 +392,13 @@
sco_sock_clear_timer(sk);
lock_sock(sk);
conn = sco_pi(sk)->conn;
- BT_DBG("sk %p state %d conn %p socket %p", sk, sk->sk_state, conn, sk->sk_socket);
+ BT_DBG("sk %p state %d conn %p socket %p refcnt %d", sk, sk->sk_state, conn, sk->sk_socket, atomic_read(&sk->sk_refcnt));
switch (sk->sk_state) {
case BT_LISTEN:
sco_sock_cleanup_listen(sk);
break;
@@ -426,12 +442,21 @@
return NULL;
sock_init_data(sock, sk);
INIT_LIST_HEAD(&bt_sk(sk)->accept_q);
sk->sk_destruct = sco_sock_destruct;
+ sk->sk_write_space = sco_sock_write_space;
+
+ /* Put sensible values for a voice link (i.e. not too big),
+ as sysctl_rmem_default & sysctl_wmem_default are
+ really not designed for that -- In our case we use sk_**buf to
+ store a count of SCO packets, not a number of bytes as most of other type of
+ sockets do */
+ sk->sk_sndbuf = DEFAULT_SCO_TXBUFS;
+ sk->sk_rcvbuf = DEFAULT_SCO_RXBUFS;
sk->sk_sndtimeo = SCO_CONN_TIMEOUT;
sock_reset_flag(sk, SOCK_ZAPPED);
sk->sk_protocol = proto;
sk->sk_state = BT_OPEN;
@@ -631,43 +656,100 @@
static int sco_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t len)
{
struct sock *sk = sock->sk;
int err = 0;
+ struct sk_buff * skb;
+ struct sco_conn *conn = 0;
- BT_DBG("sock %p, sk %p", sock, sk);
+ BT_DBG("sock %p, sk %p, len %d", sock, sk, len);
err = sock_error(sk);
if (err)
return err;
if (msg->msg_flags & MSG_OOB)
return -EOPNOTSUPP;
+
lock_sock(sk);
- if (sk->sk_state == BT_CONNECTED)
- err = sco_send_frame(sk, msg, len);
- else
- err = -ENOTCONN;
+ conn = sco_pi(sk)->conn;
+
+ /* Check outgoing MTU and that this is a 16 bytes multiple .
+ 16 bytes multiple is required by the new SCO scheduler algorithm */
+ if (len <= conn->mtu && (len % 16) == 0) {
+ if (sk->sk_state == BT_CONNECTED) {
+ /* This is the call that will put us to sleep if socket send queue is full */
+ skb = sco_skb_send_alloc(sk, len,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+
+ if (skb) {
+ err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+ if (err == 0) {
+
+ BT_DBG("skb %p, size %d", skb, len);
+
+ skb_queue_tail(&sk->sk_write_queue, skb);
+ hci_stream_sco(conn->hcon);
+ err = len;
+ }
+ else {
+ kfree_skb(skb);
+ }
+ }
+ }
+ else {
+ err = -ENOTCONN;
+ }
+ }
+ else {
+ err = -EINVAL;
+ }
release_sock(sk);
+
return err;
}
static int sco_sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, int optlen)
{
struct sock *sk = sock->sk;
+ u32 opt;
int err = 0;
BT_DBG("sk %p", sk);
lock_sock(sk);
switch (optname) {
+ case SCO_TXBUFS:
+ if (get_user(opt, (u32 __user *) optval)) {
+ err = -EFAULT;
+ break;
+ }
+ if(opt > MAX_SCO_TXBUFS) {
+ err = -EINVAL;
+ break;
+ }
+
+ sk->sk_sndbuf = opt;
+ break;
+ case SCO_RXBUFS:
+ if (get_user(opt, (u32 __user *) optval)) {
+ err = -EFAULT;
+ break;
+ }
+ if(opt > MAX_SCO_RXBUFS) {
+ err = -EINVAL;
+ break;
+ }
+
+ sk->sk_rcvbuf = opt;
+ break;
default:
err = -ENOPROTOOPT;
break;
}
release_sock(sk);
@@ -676,22 +758,41 @@
static int sco_sock_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
{
struct sock *sk = sock->sk;
struct sco_options opts;
struct sco_conninfo cinfo;
- int len, err = 0;
+ int len, err = 0;
+ int val;
BT_DBG("sk %p", sk);
if (get_user(len, optlen))
return -EFAULT;
lock_sock(sk);
switch (optname) {
+ case SCO_RXBUFS:
+ val = sk->sk_rcvbuf;
+
+ len = min_t(unsigned int, len, sizeof(val));
+ if (copy_to_user(optval, (char *)&val, len))
+ err = -EFAULT;
+
+ break;
+
+ case SCO_TXBUFS:
+ val = sk->sk_sndbuf;
+
+ len = min_t(unsigned int, len, sizeof(val));
+ if (copy_to_user(optval, (char *)&val, len))
+ err = -EFAULT;
+
+ break;
+
case SCO_OPTIONS:
if (sk->sk_state != BT_CONNECTED) {
err = -ENOTCONN;
break;
}
@@ -890,12 +991,169 @@
drop:
kfree_skb(skb);
return 0;
}
+struct sk_buff* sco_get_scodata(struct hci_conn *hcon)
+{
+ struct sco_conn *conn = hcon->sco_data;
+ struct sk_buff* skb = NULL;
+
+ BT_DBG("conn %p", conn);
+
+ if (conn) {
+ struct sock *sk = sco_chan_get(conn);
+ if(sk) {
+ skb = skb_dequeue(&sk->sk_write_queue);
+ }
+ }
+
+ if(skb == NULL && tx_quality_tune) {
+ printk(KERN_INFO "tx_quality_tune: audio cut !\n");
+ }
+
+ return skb;
+}
+
+/* Generic socket manipulations functions, adapted for SCO */
+
+static void sco_sock_wfree(struct sk_buff *skb)
+{
+ struct sock *sk = skb->sk;
+
+ atomic_sub(1, &sk->sk_wmem_alloc);
+ if (!sock_flag(sk, SOCK_USE_WRITE_QUEUE))
+ sk->sk_write_space(sk);
+ sock_put(sk);
+}
+
+static void sco_sock_rfree(struct sk_buff *skb)
+{
+ struct sock *sk = skb->sk;
+
+ atomic_sub(1, &sk->sk_rmem_alloc);
+}
+
+
+static long sock_wait_for_wmem(struct sock * sk, long timeo)
+{
+ DEFINE_WAIT(wait);
+
+ clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
+ for (;;) {
+ if (!timeo)
+ break;
+ if (signal_pending(current))
+ break;
+ set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
+ prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
+ if (atomic_read(&sk->sk_wmem_alloc) < sk->sk_sndbuf)
+ break;
+ if (sk->sk_shutdown & SEND_SHUTDOWN)
+ break;
+ if (sk->sk_err)
+ break;
+ timeo = schedule_timeout(timeo);
+ }
+ finish_wait(sk->sk_sleep, &wait);
+ return timeo;
+}
+
+static struct sk_buff *sco_skb_send_alloc(struct sock *sk, unsigned long len,
+ int nb, int *errcode)
+{
+ struct sk_buff *skb;
+ gfp_t gfp_mask;
+ int err;
+ long timeo = nb ? 0 : MAX_SCHEDULE_TIMEOUT;
+
+ gfp_mask = sk->sk_allocation;
+ if (gfp_mask & __GFP_WAIT)
+ gfp_mask |= __GFP_REPEAT;
+
+ while (1) {
+ err = sock_error(sk);
+ if (err != 0)
+ goto failure;
+
+ err = -EPIPE;
+ if (sk->sk_shutdown & SEND_SHUTDOWN)
+ goto failure;
+
+ if (atomic_read(&sk->sk_wmem_alloc) < sk->sk_sndbuf) {
+ skb = alloc_skb(len + BT_SKB_RESERVE, sk->sk_allocation);
+ if (skb) {
+ /* Full success... */
+ break;
+ }
+ err = -ENOBUFS;
+ goto failure;
+ }
+ set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
+ set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
+ err = -EAGAIN;
+ if (!timeo)
+ goto failure;
+ if (signal_pending(current))
+ goto interrupted;
+ timeo = sock_wait_for_wmem(sk, timeo);
+ }
+
+ sock_hold(sk);
+ skb->sk = sk;
+ skb->destructor = sco_sock_wfree;
+ atomic_add(1, &sk->sk_wmem_alloc);
+ skb_reserve(skb, BT_SKB_RESERVE);
+ bt_cb(skb)->incoming = 0;
+
+ return skb;
+
+interrupted:
+ err = sock_intr_errno(timeo);
+failure:
+ *errcode = err;
+ return NULL;
+}
+
+static int sco_sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
+{
+ int err = 0;
+ int skb_len;
+
+ /* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
+ number of warnings when compiling with -W --ANK
+ */
+ if (atomic_read(&sk->sk_rmem_alloc) >=
+ (unsigned)sk->sk_rcvbuf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ skb->dev = NULL;
+ skb->sk = sk;
+ skb->destructor = sco_sock_rfree;
+ atomic_add(1, &sk->sk_rmem_alloc);
+
+ /* Cache the SKB length before we tack it onto the receive
+ * queue. Once it is added it no longer belongs to us and
+ * may be freed by other threads of control pulling packets
+ * from the queue.
+ */
+ skb_len = skb->len;
+
+ skb_queue_tail(&sk->sk_receive_queue, skb);
+
+ if (!sock_flag(sk, SOCK_DEAD))
+ sk->sk_data_ready(sk, skb_len);
+out:
+ return err;
+}
+
+/* ------- Others ------- */
+
static ssize_t sco_sysfs_show(struct class *dev, char *buf)
{
struct sock *sk;
struct hlist_node *node;
char *str = buf;
@@ -943,13 +1201,14 @@
static struct hci_proto sco_hci_proto = {
.name = "SCO",
.id = HCI_PROTO_SCO,
.connect_ind = sco_connect_ind,
.connect_cfm = sco_connect_cfm,
.disconn_ind = sco_disconn_ind,
- .recv_scodata = sco_recv_scodata
+ .recv_scodata = sco_recv_scodata,
+ .get_scodata = sco_get_scodata
};
static int __init sco_init(void)
{
int err;
@@ -995,11 +1254,17 @@
proto_unregister(&sco_proto);
}
module_init(sco_init);
module_exit(sco_exit);
+module_param(tx_quality_tune, bool, 0644);
+MODULE_PARM_DESC(tx_quality_tune, "Print warning message when an audio cut will happen in tx");
+
+module_param(rx_quality_tune, bool, 0644);
+MODULE_PARM_DESC(rx_quality_tune, "Print warning message when an audio cut will happen in rx");
+
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Bluetooth SCO ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");
MODULE_ALIAS("bt-proto-2");
[-- Attachment #4: bluez-sco-flowcontrol-3of3.diff --]
[-- Type: text/plain, Size: 2315 bytes --]
diff -rU 6 --exclude-from=kernelexcludes.txt --exclude='sco.*' /home/fchevalier/tmp/linux-2.6.16.16-orig/include/net/bluetooth/hci.h /home/fchevalier/tmp/linux-2.6.16.16/include/net/bluetooth/hci.h
--- /home/fchevalier/tmp/linux-2.6.16.16-orig/include/net/bluetooth/hci.h 2006-05-11 03:56:24.000000000 +0200
+++ /home/fchevalier/tmp/linux-2.6.16.16/include/net/bluetooth/hci.h 2006-05-21 15:55:23.000000000 +0200
@@ -712,12 +712,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;
@@ -727,15 +730,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.16.16-orig/include/net/bluetooth/hci_core.h /home/fchevalier/tmp/linux-2.6.16.16/include/net/bluetooth/hci_core.h
--- /home/fchevalier/tmp/linux-2.6.16.16-orig/include/net/bluetooth/hci_core.h 2006-05-11 03:56:24.000000000 +0200
+++ /home/fchevalier/tmp/linux-2.6.16.16/include/net/bluetooth/hci_core.h 2006-05-25 17:47:11.000000000 +0200
@@ -81,18 +81,24 @@
__u16 link_policy;
__u16 link_mode;
unsigned long quirks;
atomic_t cmd_cnt;
+ /* Number of available controller buffers for ACL packets */
unsigned int acl_cnt;
+ /* Number of available controller buffers for SCO packets */
unsigned int 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;
[-- Attachment #5: Type: text/plain, Size: 0 bytes --]
[-- Attachment #6: Type: text/plain, Size: 164 bytes --]
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel
next prev parent reply other threads:[~2006-05-30 13:28 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2006-05-29 19:46 [Bluez-devel] [PATCH] SCO flow control Fabien Chevalier
2006-05-29 20:33 ` Marcel Holtmann
2006-05-29 21:39 ` Pieter Poorthuis
2006-05-29 22:21 ` Marcel Holtmann
2006-05-30 13:28 ` Fabien Chevalier [this message]
2006-06-01 19:15 ` Marcel Holtmann
2006-06-07 20:15 ` Fabien Chevalier
2006-06-07 20:30 ` Marcel Holtmann
2006-06-09 17:05 ` Fabien Chevalier
2006-06-09 20:17 ` Marcel Holtmann
2006-06-10 9:24 ` Fabien Chevalier
2006-06-10 22:22 ` Marcel Holtmann
2006-06-10 9:59 ` Fabien Chevalier
2006-06-10 23:14 ` Marcel Holtmann
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=447C4862.5050205@free.fr \
--to=fabchevalier@free.fr \
--cc=bluez-devel@lists.sourceforge.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).