diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 10a3eec..414313a 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -767,6 +767,9 @@ struct hci_dev_stats { __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]; @@ -782,9 +785,13 @@ struct hci_dev_info { __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; diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index c0fc396..5bad7c0 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -25,6 +25,8 @@ #ifndef __HCI_CORE_H #define __HCI_CORE_H +#include + #include /* HCI upper protocols */ @@ -88,12 +90,18 @@ struct hci_dev { 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; @@ -145,12 +153,12 @@ 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 attempt; @@ -162,10 +170,17 @@ struct hci_conn { __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; + + /* tx timer : used only for SCO */ + struct hrtimer tx_timer; + /* Disconnect timer */ struct timer_list disc_timer; struct timer_list idle_timer; @@ -175,6 +190,7 @@ struct hci_conn { struct hci_dev *hdev; void *l2cap_data; + /* private use for sco */ void *sco_data; void *priv; diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 6cd5711..979464d 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -184,7 +184,9 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst) conn->power_save = 1; - skb_queue_head_init(&conn->data_q); + 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; @@ -195,6 +197,7 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst) conn->idle_timer.data = (unsigned long) conn; atomic_set(&conn->refcnt, 0); + atomic_set(&conn->sent, 0); hci_dev_hold(hdev); @@ -221,6 +224,8 @@ int hci_conn_del(struct hci_conn *conn) del_timer(&conn->disc_timer); + hrtimer_cancel(&conn->tx_timer); + if (conn->type == SCO_LINK) { struct hci_conn *acl = conn->link; if (acl) { @@ -233,7 +238,7 @@ int hci_conn_del(struct hci_conn *conn) sco->link = NULL; /* Unacked frames */ - hdev->acl_cnt += conn->sent; + hdev->acl_cnt += atomic_read(&conn->sent); } tasklet_disable(&hdev->tx_task); @@ -246,7 +251,7 @@ int hci_conn_del(struct hci_conn *conn) tasklet_enable(&hdev->tx_task); - skb_queue_purge(&conn->data_q); + skb_queue_purge(&conn->out_q); hci_dev_put(hdev); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 338ae97..a2627af 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -619,8 +619,9 @@ int hci_dev_reset(__u16 dev) 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, @@ -1012,7 +1013,7 @@ static int hci_send_frame(struct sk_buff *skb) hci_send_to_sock(hdev, skb); } - /* Get rid of skb owner, prior to sending to the driver. */ + /* Get rid of skb owner, prior to sending to the driver. */ skb_orphan(skb); return hdev->send(skb); @@ -1096,7 +1097,7 @@ int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags) /* 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); @@ -1104,9 +1105,9 @@ int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags) 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; @@ -1116,10 +1117,10 @@ int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags) 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); @@ -1132,14 +1133,25 @@ int 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); + ktime_t now = conn->tx_timer.base->get_time(); +#ifdef CONFIG_BT_HCI_CORE_DEBUG + ktime_t timer_exp = conn->tx_timer.expires; + BT_DBG("conn %p skb %p, timer %5lu.%06lu", conn, skb, + (unsigned long) timer_exp.tv64, + do_div(timer_exp.tv64, NSEC_PER_SEC) / 1000); +#endif if (skb->len > hdev->sco_mtu) { kfree_skb(skb); return -EINVAL; } + /* Criteria for underrun condition : more than 100 ms late */ + if(conn->tx_timer.expires.tv64 + NSEC_PER_SEC / 10 <= now.tv64) { + /* We are under underrun condition, just we do a clean start */ + conn->tx_timer.expires = now; + } + hdr.handle = __cpu_to_le16(conn->handle); hdr.dlen = skb->len; @@ -1148,7 +1160,7 @@ int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb) skb->dev = (void *) hdev; bt_cb(skb)->pkt_type = HCI_SCODATA_PKT; - skb_queue_tail(&conn->data_q, skb); + skb_queue_tail(&conn->out_q, skb); hci_sched_tx(hdev); return 0; } @@ -1156,12 +1168,12 @@ 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 @@ -1170,20 +1182,22 @@ static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int 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; @@ -1203,7 +1217,7 @@ static inline void hci_acl_tx_to(struct hci_dev *hdev) /* 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); @@ -1226,8 +1240,8 @@ static inline void hci_sched_acl(struct hci_dev *hdev) 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); @@ -1236,28 +1250,79 @@ static inline void hci_sched_acl(struct hci_dev *hdev) 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(); - 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); +#endif - 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); + } + 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(); + + 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); + } + } + } } } } @@ -1269,14 +1334,14 @@ 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", 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); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index bb94e6d..815c79d 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -320,7 +320,7 @@ static void hci_cc_info_param(struct hci_dev *hdev, __u16 ocf, struct sk_buff *s 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; } @@ -382,7 +382,7 @@ static void hci_cc_info_param(struct hci_dev *hdev, __u16 ocf, struct sk_buff *s } 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); @@ -880,15 +880,15 @@ static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *s 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); diff --git a/include/net/bluetooth/sco.h b/include/net/bluetooth/sco.h index e28a2a7..4ba976b 100644 --- a/include/net/bluetooth/sco.h +++ b/include/net/bluetooth/sco.h @@ -26,12 +26,7 @@ #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 { @@ -51,6 +46,9 @@ struct sco_conninfo { __u8 dev_class[3]; }; +#define SCO_TXBUFS 0x03 +#define SCO_RXBUFS 0x04 + /* ---- SCO connections ---- */ struct sco_conn { struct hci_conn *hcon; diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index 5d13d4f..50f7590 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -53,7 +53,13 @@ #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 const struct proto_ops sco_sock_ops; @@ -61,6 +67,8 @@ 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); @@ -69,6 +77,35 @@ 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 int sco_sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb); + +/* + * Write buffer destructor automatically called from kfree_skb. + */ +void sco_sock_wfree(struct sk_buff *skb) +{ + struct sock *sk = skb->sk; + + atomic_dec(&sk->sk_wmem_alloc); + sk->sk_write_space(sk); + sock_put(sk); +} + +static void sco_sock_write_space(struct sock *sk) +{ + read_lock(&sk->sk_callback_lock); + + 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); + + if (sock_writeable(sk)) + sk_wake_async(sk, 2, POLL_OUT); + } + + read_unlock(&sk->sk_callback_lock); +} + /* ---- SCO timers ---- */ static void sco_sock_timeout(unsigned long arg) { @@ -234,27 +271,30 @@ 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; + int err; 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))) + if (!(skb = bt_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err))) return err; - if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) { + /* fix sk_wmem_alloc value : by default it is increased by skb->truesize, but + we want it only increased by 1 */ + atomic_sub(skb->truesize - 1, &sk->sk_wmem_alloc); + /* fix destructor */ + skb->destructor = sco_sock_wfree; + + if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) { err = -EFAULT; goto fail; } - if ((err = hci_send_sco(conn->hcon, skb)) < 0) - return err; + err = hci_send_sco(conn->hcon, skb); - return count; + if (err < 0) + goto fail; + + return len; fail: kfree_skb(skb); @@ -273,8 +313,9 @@ static inline void sco_recv_frame(struct sco_conn *conn, struct sk_buff *skb) 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; + } drop: kfree_skb(skb); @@ -328,7 +369,6 @@ 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_cleanup_listen(struct sock *parent) @@ -360,6 +400,8 @@ static void sco_sock_kill(struct sock *sk) /* Kill poor orphan */ bt_sock_unlink(&sco_sk_list, sk); sock_set_flag(sk, SOCK_DEAD); + + /* release socket */ sock_put(sk); } @@ -376,7 +418,7 @@ static void sco_sock_close(struct 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: @@ -426,6 +468,15 @@ static struct sock *sco_sock_alloc(struct socket *sock, int proto, gfp_t prio) 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); @@ -656,6 +707,7 @@ static int sco_sock_sendmsg(struct kiocb *iocb, struct socket *sock, 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); @@ -663,6 +715,35 @@ static int sco_sock_setsockopt(struct socket *sock, int level, int optname, char 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; + /* + * Wake up sending tasks if we + * upped the value. + */ + sk->sk_write_space(sk); + 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; @@ -677,7 +758,8 @@ static int sco_sock_getsockopt(struct socket *sock, int level, int optname, char 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); @@ -687,6 +769,24 @@ static int sco_sock_getsockopt(struct socket *sock, int level, int optname, char 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; @@ -891,6 +991,34 @@ drop: return 0; } +static int sco_sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + int err = 0; + + BT_DBG("sock %p, sk_rcvbuf %d, qlen %d", sk, sk->sk_rcvbuf, skb_queue_len(&sk->sk_receive_queue)); + /* Cast skb->rcvbuf to unsigned... It's pointless, but reduces + number of warnings when compiling with -W --ANK + */ + if (skb_queue_len(&sk->sk_receive_queue) + 1 > + (unsigned)sk->sk_rcvbuf) { + err = -ENOMEM; + goto out; + } + + skb->dev = NULL; + skb->sk = sk; + skb->destructor = NULL; + + skb_queue_tail(&sk->sk_receive_queue, skb); + + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_data_ready(sk, 1); +out: + return err; +} + +/* ------- Others ------- */ + static ssize_t sco_sysfs_show(struct class *dev, char *buf) { struct sock *sk;