2007-02-09 15:24:33 +01:00
|
|
|
/*
|
2005-04-17 00:20:36 +02:00
|
|
|
BlueZ - Bluetooth protocol stack for Linux
|
|
|
|
Copyright (C) 2000-2001 Qualcomm Incorporated
|
2010-07-13 16:57:11 +02:00
|
|
|
Copyright (C) 2009-2010 Gustavo F. Padovan <gustavo@padovan.org>
|
2010-07-16 21:18:39 +02:00
|
|
|
Copyright (C) 2010 Google Inc.
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License version 2 as
|
|
|
|
published by the Free Software Foundation;
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
|
|
|
|
IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
|
2007-02-09 15:24:33 +01:00
|
|
|
CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
|
|
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
2005-04-17 00:20:36 +02:00
|
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
2007-02-09 15:24:33 +01:00
|
|
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
|
|
|
|
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
|
2005-04-17 00:20:36 +02:00
|
|
|
SOFTWARE IS DISCLAIMED.
|
|
|
|
*/
|
|
|
|
|
2011-02-03 23:50:35 +01:00
|
|
|
/* Bluetooth L2CAP core. */
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
|
|
|
|
#include <linux/types.h>
|
2006-01-11 21:17:47 +01:00
|
|
|
#include <linux/capability.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/poll.h>
|
|
|
|
#include <linux/fcntl.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/socket.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/list.h>
|
2005-11-08 18:57:38 +01:00
|
|
|
#include <linux/device.h>
|
2010-03-21 05:27:45 +01:00
|
|
|
#include <linux/debugfs.h>
|
|
|
|
#include <linux/seq_file.h>
|
2009-04-20 06:31:08 +02:00
|
|
|
#include <linux/uaccess.h>
|
2009-08-21 03:26:02 +02:00
|
|
|
#include <linux/crc16.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <net/sock.h>
|
|
|
|
|
|
|
|
#include <asm/system.h>
|
|
|
|
#include <asm/unaligned.h>
|
|
|
|
|
|
|
|
#include <net/bluetooth/bluetooth.h>
|
|
|
|
#include <net/bluetooth/hci_core.h>
|
|
|
|
#include <net/bluetooth/l2cap.h>
|
2011-06-07 23:46:31 +02:00
|
|
|
#include <net/bluetooth/smp.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-02-03 23:50:35 +01:00
|
|
|
int disable_ertm;
|
2011-09-16 15:26:32 +02:00
|
|
|
int enable_hs;
|
2007-10-20 13:38:51 +02:00
|
|
|
|
2009-05-03 03:57:55 +02:00
|
|
|
static u32 l2cap_feat_mask = L2CAP_FEAT_FIXED_CHAN;
|
2011-11-03 00:18:34 +01:00
|
|
|
static u8 l2cap_fixed_chan[8] = { L2CAP_FC_L2CAP, };
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-06-01 08:54:45 +02:00
|
|
|
static LIST_HEAD(chan_list);
|
|
|
|
static DEFINE_RWLOCK(chan_list_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
|
|
|
|
u8 code, u8 ident, u16 dlen, void *data);
|
2011-04-28 22:55:53 +02:00
|
|
|
static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len,
|
|
|
|
void *data);
|
2011-03-25 18:30:37 +01:00
|
|
|
static int l2cap_build_conf_req(struct l2cap_chan *chan, void *data);
|
2011-04-28 22:55:53 +02:00
|
|
|
static void l2cap_send_disconn_req(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_chan *chan, int err);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2010-06-21 23:53:22 +02:00
|
|
|
static int l2cap_ertm_data_rcv(struct sock *sk, struct sk_buff *skb);
|
|
|
|
|
2006-07-03 10:02:46 +02:00
|
|
|
/* ---- L2CAP channels ---- */
|
2011-05-17 19:34:52 +02:00
|
|
|
|
|
|
|
static inline void chan_hold(struct l2cap_chan *c)
|
|
|
|
{
|
|
|
|
atomic_inc(&c->refcnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void chan_put(struct l2cap_chan *c)
|
|
|
|
{
|
|
|
|
if (atomic_dec_and_test(&c->refcnt))
|
|
|
|
kfree(c);
|
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
static struct l2cap_chan *__l2cap_get_chan_by_dcid(struct l2cap_conn *conn, u16 cid)
|
2006-07-03 10:02:46 +02:00
|
|
|
{
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *c;
|
2011-03-31 21:17:41 +02:00
|
|
|
|
|
|
|
list_for_each_entry(c, &conn->chan_l, list) {
|
2011-04-14 00:50:45 +02:00
|
|
|
if (c->dcid == cid)
|
2011-03-31 21:17:41 +02:00
|
|
|
return c;
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
2011-03-31 21:17:41 +02:00
|
|
|
return NULL;
|
|
|
|
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
static struct l2cap_chan *__l2cap_get_chan_by_scid(struct l2cap_conn *conn, u16 cid)
|
2006-07-03 10:02:46 +02:00
|
|
|
{
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *c;
|
2011-03-31 21:17:41 +02:00
|
|
|
|
|
|
|
list_for_each_entry(c, &conn->chan_l, list) {
|
2011-04-14 00:50:45 +02:00
|
|
|
if (c->scid == cid)
|
2011-03-31 21:17:41 +02:00
|
|
|
return c;
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
2011-03-31 21:17:41 +02:00
|
|
|
return NULL;
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Find channel with given SCID.
|
|
|
|
* Returns locked socket */
|
2011-03-31 21:17:41 +02:00
|
|
|
static struct l2cap_chan *l2cap_get_chan_by_scid(struct l2cap_conn *conn, u16 cid)
|
2006-07-03 10:02:46 +02:00
|
|
|
{
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *c;
|
2011-03-31 21:17:41 +02:00
|
|
|
|
|
|
|
read_lock(&conn->chan_lock);
|
|
|
|
c = __l2cap_get_chan_by_scid(conn, cid);
|
2011-03-25 04:22:30 +01:00
|
|
|
if (c)
|
|
|
|
bh_lock_sock(c->sk);
|
2011-03-31 21:17:41 +02:00
|
|
|
read_unlock(&conn->chan_lock);
|
2011-03-25 04:22:30 +01:00
|
|
|
return c;
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
static struct l2cap_chan *__l2cap_get_chan_by_ident(struct l2cap_conn *conn, u8 ident)
|
2006-07-03 10:02:46 +02:00
|
|
|
{
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *c;
|
2011-03-31 21:17:41 +02:00
|
|
|
|
|
|
|
list_for_each_entry(c, &conn->chan_l, list) {
|
2011-03-25 17:59:37 +01:00
|
|
|
if (c->ident == ident)
|
2011-03-31 21:17:41 +02:00
|
|
|
return c;
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
2011-03-31 21:17:41 +02:00
|
|
|
return NULL;
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
static inline struct l2cap_chan *l2cap_get_chan_by_ident(struct l2cap_conn *conn, u8 ident)
|
2006-07-03 10:02:46 +02:00
|
|
|
{
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *c;
|
2011-03-31 21:17:41 +02:00
|
|
|
|
|
|
|
read_lock(&conn->chan_lock);
|
|
|
|
c = __l2cap_get_chan_by_ident(conn, ident);
|
2011-03-25 04:22:30 +01:00
|
|
|
if (c)
|
|
|
|
bh_lock_sock(c->sk);
|
2011-03-31 21:17:41 +02:00
|
|
|
read_unlock(&conn->chan_lock);
|
2011-03-25 04:22:30 +01:00
|
|
|
return c;
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
static struct l2cap_chan *__l2cap_global_chan_by_addr(__le16 psm, bdaddr_t *src)
|
2011-04-18 23:38:43 +02:00
|
|
|
{
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *c;
|
2011-04-18 23:38:43 +02:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
list_for_each_entry(c, &chan_list, global_l) {
|
|
|
|
if (c->sport == psm && !bacmp(&bt_sk(c->sk)->src, src))
|
2011-04-18 23:38:43 +02:00
|
|
|
goto found;
|
|
|
|
}
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
c = NULL;
|
2011-04-18 23:38:43 +02:00
|
|
|
found:
|
2011-04-27 23:26:32 +02:00
|
|
|
return c;
|
2011-04-18 23:38:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm)
|
|
|
|
{
|
2011-04-19 00:36:44 +02:00
|
|
|
int err;
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
write_lock_bh(&chan_list_lock);
|
2011-04-18 23:38:43 +02:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
if (psm && __l2cap_global_chan_by_addr(psm, src)) {
|
2011-04-19 00:36:44 +02:00
|
|
|
err = -EADDRINUSE;
|
|
|
|
goto done;
|
2011-04-18 23:38:43 +02:00
|
|
|
}
|
|
|
|
|
2011-04-19 00:36:44 +02:00
|
|
|
if (psm) {
|
|
|
|
chan->psm = psm;
|
|
|
|
chan->sport = psm;
|
|
|
|
err = 0;
|
|
|
|
} else {
|
|
|
|
u16 p;
|
|
|
|
|
|
|
|
err = -EINVAL;
|
|
|
|
for (p = 0x1001; p < 0x1100; p += 2)
|
2011-04-27 23:26:32 +02:00
|
|
|
if (!__l2cap_global_chan_by_addr(cpu_to_le16(p), src)) {
|
2011-04-19 00:36:44 +02:00
|
|
|
chan->psm = cpu_to_le16(p);
|
|
|
|
chan->sport = cpu_to_le16(p);
|
|
|
|
err = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-04-18 23:38:43 +02:00
|
|
|
|
2011-04-19 00:36:44 +02:00
|
|
|
done:
|
2011-04-27 23:26:32 +02:00
|
|
|
write_unlock_bh(&chan_list_lock);
|
2011-04-19 00:36:44 +02:00
|
|
|
return err;
|
2011-04-18 23:38:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid)
|
|
|
|
{
|
2011-04-27 23:26:32 +02:00
|
|
|
write_lock_bh(&chan_list_lock);
|
2011-04-18 23:38:43 +02:00
|
|
|
|
|
|
|
chan->scid = scid;
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
write_unlock_bh(&chan_list_lock);
|
2011-04-18 23:38:43 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
static u16 l2cap_alloc_cid(struct l2cap_conn *conn)
|
2006-07-03 10:02:46 +02:00
|
|
|
{
|
2009-04-20 06:31:05 +02:00
|
|
|
u16 cid = L2CAP_CID_DYN_START;
|
2006-07-03 10:02:46 +02:00
|
|
|
|
2009-04-20 06:31:05 +02:00
|
|
|
for (; cid < L2CAP_CID_DYN_END; cid++) {
|
2011-03-31 21:17:41 +02:00
|
|
|
if (!__l2cap_get_chan_by_scid(conn, cid))
|
2006-07-03 10:02:46 +02:00
|
|
|
return cid;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-05-17 19:59:01 +02:00
|
|
|
static void l2cap_set_timer(struct l2cap_chan *chan, struct timer_list *timer, long timeout)
|
2011-05-02 23:25:01 +02:00
|
|
|
{
|
2011-10-31 15:17:21 +01:00
|
|
|
BT_DBG("chan %p state %d timeout %ld", chan, chan->state, timeout);
|
2011-06-03 05:19:47 +02:00
|
|
|
|
2011-06-29 23:35:21 +02:00
|
|
|
if (!mod_timer(timer, jiffies + msecs_to_jiffies(timeout)))
|
2011-06-29 23:35:20 +02:00
|
|
|
chan_hold(chan);
|
2011-05-02 23:25:01 +02:00
|
|
|
}
|
|
|
|
|
2011-05-17 19:59:01 +02:00
|
|
|
static void l2cap_clear_timer(struct l2cap_chan *chan, struct timer_list *timer)
|
2011-05-02 23:25:01 +02:00
|
|
|
{
|
2011-06-29 23:35:20 +02:00
|
|
|
BT_DBG("chan %p state %d", chan, chan->state);
|
2011-05-02 23:25:01 +02:00
|
|
|
|
2011-06-29 23:35:20 +02:00
|
|
|
if (timer_pending(timer) && del_timer(timer))
|
|
|
|
chan_put(chan);
|
2011-05-02 23:25:01 +02:00
|
|
|
}
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
static void l2cap_state_change(struct l2cap_chan *chan, int state)
|
|
|
|
{
|
|
|
|
chan->state = state;
|
|
|
|
chan->ops->state_change(chan->data, state);
|
|
|
|
}
|
|
|
|
|
2011-05-02 23:25:01 +02:00
|
|
|
static void l2cap_chan_timeout(unsigned long arg)
|
|
|
|
{
|
|
|
|
struct l2cap_chan *chan = (struct l2cap_chan *) arg;
|
|
|
|
struct sock *sk = chan->sk;
|
|
|
|
int reason;
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
BT_DBG("chan %p state %d", chan, chan->state);
|
2011-05-02 23:25:01 +02:00
|
|
|
|
|
|
|
bh_lock_sock(sk);
|
|
|
|
|
|
|
|
if (sock_owned_by_user(sk)) {
|
|
|
|
/* sk is owned by user. Try again later */
|
2011-11-07 20:19:04 +01:00
|
|
|
__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
|
2011-05-02 23:25:01 +02:00
|
|
|
bh_unlock_sock(sk);
|
2011-05-17 19:34:52 +02:00
|
|
|
chan_put(chan);
|
2011-05-02 23:25:01 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (chan->state == BT_CONNECTED || chan->state == BT_CONFIG)
|
2011-05-02 23:25:01 +02:00
|
|
|
reason = ECONNREFUSED;
|
2011-06-03 05:19:47 +02:00
|
|
|
else if (chan->state == BT_CONNECT &&
|
2011-05-02 23:25:01 +02:00
|
|
|
chan->sec_level != BT_SECURITY_SDP)
|
|
|
|
reason = ECONNREFUSED;
|
|
|
|
else
|
|
|
|
reason = ETIMEDOUT;
|
|
|
|
|
2011-05-05 00:42:50 +02:00
|
|
|
l2cap_chan_close(chan, reason);
|
2011-05-02 23:25:01 +02:00
|
|
|
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
|
2011-05-16 23:23:24 +02:00
|
|
|
chan->ops->close(chan->data);
|
2011-05-17 19:34:52 +02:00
|
|
|
chan_put(chan);
|
2011-05-02 23:25:01 +02:00
|
|
|
}
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *l2cap_chan_create(struct sock *sk)
|
2011-03-25 04:22:30 +01:00
|
|
|
{
|
|
|
|
struct l2cap_chan *chan;
|
|
|
|
|
|
|
|
chan = kzalloc(sizeof(*chan), GFP_ATOMIC);
|
|
|
|
if (!chan)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
chan->sk = sk;
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
write_lock_bh(&chan_list_lock);
|
|
|
|
list_add(&chan->global_l, &chan_list);
|
|
|
|
write_unlock_bh(&chan_list_lock);
|
|
|
|
|
2011-05-02 23:25:01 +02:00
|
|
|
setup_timer(&chan->chan_timer, l2cap_chan_timeout, (unsigned long) chan);
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
chan->state = BT_OPEN;
|
|
|
|
|
2011-05-17 19:34:52 +02:00
|
|
|
atomic_set(&chan->refcnt, 1);
|
|
|
|
|
2011-11-03 16:05:44 +01:00
|
|
|
BT_DBG("sk %p chan %p", sk, chan);
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
void l2cap_chan_destroy(struct l2cap_chan *chan)
|
2011-04-25 20:10:41 +02:00
|
|
|
{
|
2011-04-27 23:26:32 +02:00
|
|
|
write_lock_bh(&chan_list_lock);
|
|
|
|
list_del(&chan->global_l);
|
|
|
|
write_unlock_bh(&chan_list_lock);
|
|
|
|
|
2011-05-17 19:34:52 +02:00
|
|
|
chan_put(chan);
|
2011-04-25 20:10:41 +02:00
|
|
|
}
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
static void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
|
2006-07-03 10:02:46 +02:00
|
|
|
{
|
2009-04-20 06:31:08 +02:00
|
|
|
BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
|
2011-04-14 00:50:45 +02:00
|
|
|
chan->psm, chan->dcid);
|
2006-07-03 10:02:46 +02:00
|
|
|
|
2011-11-07 13:20:25 +01:00
|
|
|
conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM;
|
2009-02-12 14:02:50 +01:00
|
|
|
|
2011-04-14 01:23:55 +02:00
|
|
|
chan->conn = conn;
|
2006-07-03 10:02:46 +02:00
|
|
|
|
2011-05-02 22:13:55 +02:00
|
|
|
if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED) {
|
2011-02-11 02:38:50 +01:00
|
|
|
if (conn->hcon->type == LE_LINK) {
|
|
|
|
/* LE connection */
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->omtu = L2CAP_LE_DEFAULT_MTU;
|
2011-04-14 00:50:45 +02:00
|
|
|
chan->scid = L2CAP_CID_LE_DATA;
|
|
|
|
chan->dcid = L2CAP_CID_LE_DATA;
|
2011-02-11 02:38:50 +01:00
|
|
|
} else {
|
|
|
|
/* Alloc CID for connection-oriented socket */
|
2011-04-14 00:50:45 +02:00
|
|
|
chan->scid = l2cap_alloc_cid(conn);
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->omtu = L2CAP_DEFAULT_MTU;
|
2011-02-11 02:38:50 +01:00
|
|
|
}
|
2011-05-02 22:13:55 +02:00
|
|
|
} else if (chan->chan_type == L2CAP_CHAN_CONN_LESS) {
|
2006-07-03 10:02:46 +02:00
|
|
|
/* Connectionless socket */
|
2011-04-14 00:50:45 +02:00
|
|
|
chan->scid = L2CAP_CID_CONN_LESS;
|
|
|
|
chan->dcid = L2CAP_CID_CONN_LESS;
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->omtu = L2CAP_DEFAULT_MTU;
|
2006-07-03 10:02:46 +02:00
|
|
|
} else {
|
|
|
|
/* Raw socket can send/recv signalling messages only */
|
2011-04-14 00:50:45 +02:00
|
|
|
chan->scid = L2CAP_CID_SIGNALING;
|
|
|
|
chan->dcid = L2CAP_CID_SIGNALING;
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->omtu = L2CAP_DEFAULT_MTU;
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
|
|
|
|
2011-10-13 15:18:54 +02:00
|
|
|
chan->local_id = L2CAP_BESTEFFORT_ID;
|
|
|
|
chan->local_stype = L2CAP_SERV_BESTEFFORT;
|
|
|
|
chan->local_msdu = L2CAP_DEFAULT_MAX_SDU_SIZE;
|
|
|
|
chan->local_sdu_itime = L2CAP_DEFAULT_SDU_ITIME;
|
|
|
|
chan->local_acc_lat = L2CAP_DEFAULT_ACC_LAT;
|
|
|
|
chan->local_flush_to = L2CAP_DEFAULT_FLUSH_TO;
|
|
|
|
|
2011-05-17 19:34:52 +02:00
|
|
|
chan_hold(chan);
|
2011-03-31 21:17:41 +02:00
|
|
|
|
|
|
|
list_add(&chan->list, &conn->chan_l);
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
|
|
|
|
2007-02-09 15:24:33 +01:00
|
|
|
/* Delete channel.
|
2006-07-03 10:02:46 +02:00
|
|
|
* Must be called on the locked socket. */
|
2011-04-28 22:55:53 +02:00
|
|
|
static void l2cap_chan_del(struct l2cap_chan *chan, int err)
|
2006-07-03 10:02:46 +02:00
|
|
|
{
|
2011-03-25 04:22:30 +01:00
|
|
|
struct sock *sk = chan->sk;
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_conn *conn = chan->conn;
|
2006-07-03 10:02:46 +02:00
|
|
|
struct sock *parent = bt_sk(sk)->parent;
|
|
|
|
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2006-07-03 10:02:46 +02:00
|
|
|
|
2011-04-04 20:59:54 +02:00
|
|
|
BT_DBG("chan %p, conn %p, err %d", chan, conn, err);
|
2006-07-03 10:02:46 +02:00
|
|
|
|
2007-02-09 15:24:33 +01:00
|
|
|
if (conn) {
|
2011-03-31 21:17:41 +02:00
|
|
|
/* Delete from channel list */
|
|
|
|
write_lock_bh(&conn->chan_lock);
|
|
|
|
list_del(&chan->list);
|
|
|
|
write_unlock_bh(&conn->chan_lock);
|
2011-05-17 19:34:52 +02:00
|
|
|
chan_put(chan);
|
2011-03-31 21:17:41 +02:00
|
|
|
|
2011-04-14 01:23:55 +02:00
|
|
|
chan->conn = NULL;
|
2006-07-03 10:02:46 +02:00
|
|
|
hci_conn_put(conn->hcon);
|
|
|
|
}
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CLOSED);
|
2006-07-03 10:02:46 +02:00
|
|
|
sock_set_flag(sk, SOCK_ZAPPED);
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
sk->sk_err = err;
|
|
|
|
|
|
|
|
if (parent) {
|
|
|
|
bt_accept_unlink(sk);
|
|
|
|
parent->sk_data_ready(parent, 0);
|
|
|
|
} else
|
|
|
|
sk->sk_state_change(sk);
|
2010-05-14 01:50:12 +02:00
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (!(test_bit(CONF_OUTPUT_DONE, &chan->conf_state) &&
|
|
|
|
test_bit(CONF_INPUT_DONE, &chan->conf_state)))
|
2011-04-25 20:10:41 +02:00
|
|
|
return;
|
2011-04-01 20:13:36 +02:00
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
skb_queue_purge(&chan->tx_q);
|
2010-05-14 01:50:12 +02:00
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->mode == L2CAP_MODE_ERTM) {
|
2010-05-14 01:50:12 +02:00
|
|
|
struct srej_list *l, *tmp;
|
|
|
|
|
2011-05-17 20:13:19 +02:00
|
|
|
__clear_retrans_timer(chan);
|
|
|
|
__clear_monitor_timer(chan);
|
|
|
|
__clear_ack_timer(chan);
|
2010-05-14 01:50:12 +02:00
|
|
|
|
2011-03-26 00:36:10 +01:00
|
|
|
skb_queue_purge(&chan->srej_q);
|
2010-05-14 01:50:12 +02:00
|
|
|
|
2011-04-04 20:40:12 +02:00
|
|
|
list_for_each_entry_safe(l, tmp, &chan->srej_l, list) {
|
2010-05-14 01:50:12 +02:00
|
|
|
list_del(&l->list);
|
|
|
|
kfree(l);
|
|
|
|
}
|
|
|
|
}
|
2006-07-03 10:02:46 +02:00
|
|
|
}
|
|
|
|
|
2011-04-28 22:55:53 +02:00
|
|
|
static void l2cap_chan_cleanup_listen(struct sock *parent)
|
|
|
|
{
|
|
|
|
struct sock *sk;
|
|
|
|
|
|
|
|
BT_DBG("parent %p", parent);
|
|
|
|
|
|
|
|
/* Close not yet accepted channels */
|
2011-05-05 00:42:50 +02:00
|
|
|
while ((sk = bt_accept_dequeue(parent, NULL))) {
|
2011-05-16 23:23:24 +02:00
|
|
|
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2011-05-05 00:42:50 +02:00
|
|
|
lock_sock(sk);
|
2011-05-16 23:23:24 +02:00
|
|
|
l2cap_chan_close(chan, ECONNRESET);
|
2011-05-05 00:42:50 +02:00
|
|
|
release_sock(sk);
|
2011-05-16 23:23:24 +02:00
|
|
|
chan->ops->close(chan->data);
|
2011-05-05 00:42:50 +02:00
|
|
|
}
|
2011-04-28 22:55:53 +02:00
|
|
|
}
|
|
|
|
|
2011-05-05 00:42:50 +02:00
|
|
|
void l2cap_chan_close(struct l2cap_chan *chan, int reason)
|
2011-04-28 22:55:53 +02:00
|
|
|
{
|
|
|
|
struct l2cap_conn *conn = chan->conn;
|
|
|
|
struct sock *sk = chan->sk;
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
BT_DBG("chan %p state %d socket %p", chan, chan->state, sk->sk_socket);
|
2011-04-28 22:55:53 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
switch (chan->state) {
|
2011-04-28 22:55:53 +02:00
|
|
|
case BT_LISTEN:
|
|
|
|
l2cap_chan_cleanup_listen(sk);
|
2011-06-03 05:19:47 +02:00
|
|
|
|
|
|
|
l2cap_state_change(chan, BT_CLOSED);
|
|
|
|
sock_set_flag(sk, SOCK_ZAPPED);
|
2011-04-28 22:55:53 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case BT_CONNECTED:
|
|
|
|
case BT_CONFIG:
|
2011-05-02 22:13:55 +02:00
|
|
|
if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED &&
|
2011-04-28 22:55:53 +02:00
|
|
|
conn->hcon->type == ACL_LINK) {
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
|
|
|
__set_chan_timer(chan, sk->sk_sndtimeo);
|
2011-04-28 22:55:53 +02:00
|
|
|
l2cap_send_disconn_req(conn, chan, reason);
|
|
|
|
} else
|
|
|
|
l2cap_chan_del(chan, reason);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BT_CONNECT2:
|
2011-05-02 22:13:55 +02:00
|
|
|
if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED &&
|
2011-04-28 22:55:53 +02:00
|
|
|
conn->hcon->type == ACL_LINK) {
|
|
|
|
struct l2cap_conn_rsp rsp;
|
|
|
|
__u16 result;
|
|
|
|
|
|
|
|
if (bt_sk(sk)->defer_setup)
|
|
|
|
result = L2CAP_CR_SEC_BLOCK;
|
|
|
|
else
|
|
|
|
result = L2CAP_CR_BAD_PSM;
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_DISCONN);
|
2011-04-28 22:55:53 +02:00
|
|
|
|
|
|
|
rsp.scid = cpu_to_le16(chan->dcid);
|
|
|
|
rsp.dcid = cpu_to_le16(chan->scid);
|
|
|
|
rsp.result = cpu_to_le16(result);
|
|
|
|
rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO);
|
|
|
|
l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_RSP,
|
|
|
|
sizeof(rsp), &rsp);
|
|
|
|
}
|
|
|
|
|
|
|
|
l2cap_chan_del(chan, reason);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BT_CONNECT:
|
|
|
|
case BT_DISCONN:
|
|
|
|
l2cap_chan_del(chan, reason);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
sock_set_flag(sk, SOCK_ZAPPED);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-12 23:31:57 +02:00
|
|
|
static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan)
|
2008-07-14 20:13:44 +02:00
|
|
|
{
|
2011-05-02 22:13:55 +02:00
|
|
|
if (chan->chan_type == L2CAP_CHAN_RAW) {
|
2011-04-12 23:31:57 +02:00
|
|
|
switch (chan->sec_level) {
|
2011-01-19 07:36:50 +01:00
|
|
|
case BT_SECURITY_HIGH:
|
|
|
|
return HCI_AT_DEDICATED_BONDING_MITM;
|
|
|
|
case BT_SECURITY_MEDIUM:
|
|
|
|
return HCI_AT_DEDICATED_BONDING;
|
|
|
|
default:
|
|
|
|
return HCI_AT_NO_BONDING;
|
|
|
|
}
|
2011-04-14 00:50:45 +02:00
|
|
|
} else if (chan->psm == cpu_to_le16(0x0001)) {
|
2011-04-12 23:31:57 +02:00
|
|
|
if (chan->sec_level == BT_SECURITY_LOW)
|
|
|
|
chan->sec_level = BT_SECURITY_SDP;
|
2009-02-09 02:48:38 +01:00
|
|
|
|
2011-04-12 23:31:57 +02:00
|
|
|
if (chan->sec_level == BT_SECURITY_HIGH)
|
2011-01-19 07:36:50 +01:00
|
|
|
return HCI_AT_NO_BONDING_MITM;
|
2009-02-12 16:19:45 +01:00
|
|
|
else
|
2011-01-19 07:36:50 +01:00
|
|
|
return HCI_AT_NO_BONDING;
|
2009-02-12 16:19:45 +01:00
|
|
|
} else {
|
2011-04-12 23:31:57 +02:00
|
|
|
switch (chan->sec_level) {
|
2009-02-12 16:19:45 +01:00
|
|
|
case BT_SECURITY_HIGH:
|
2011-01-19 07:36:50 +01:00
|
|
|
return HCI_AT_GENERAL_BONDING_MITM;
|
2009-02-12 16:19:45 +01:00
|
|
|
case BT_SECURITY_MEDIUM:
|
2011-01-19 07:36:50 +01:00
|
|
|
return HCI_AT_GENERAL_BONDING;
|
2009-02-12 16:19:45 +01:00
|
|
|
default:
|
2011-01-19 07:36:50 +01:00
|
|
|
return HCI_AT_NO_BONDING;
|
2009-02-12 16:19:45 +01:00
|
|
|
}
|
2009-02-09 02:48:38 +01:00
|
|
|
}
|
2011-01-19 07:36:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Service level security */
|
2011-11-05 22:54:24 +01:00
|
|
|
int l2cap_chan_check_security(struct l2cap_chan *chan)
|
2011-01-19 07:36:50 +01:00
|
|
|
{
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_conn *conn = chan->conn;
|
2011-01-19 07:36:50 +01:00
|
|
|
__u8 auth_type;
|
|
|
|
|
2011-04-12 23:31:57 +02:00
|
|
|
auth_type = l2cap_get_auth_type(chan);
|
2008-07-14 20:13:44 +02:00
|
|
|
|
2011-04-12 23:31:57 +02:00
|
|
|
return hci_conn_security(conn->hcon, chan->sec_level, auth_type);
|
2008-07-14 20:13:44 +02:00
|
|
|
}
|
|
|
|
|
2011-06-01 08:54:45 +02:00
|
|
|
static u8 l2cap_get_ident(struct l2cap_conn *conn)
|
2007-10-20 13:37:56 +02:00
|
|
|
{
|
|
|
|
u8 id;
|
|
|
|
|
|
|
|
/* Get next available identificator.
|
|
|
|
* 1 - 128 are used by kernel.
|
|
|
|
* 129 - 199 are reserved.
|
|
|
|
* 200 - 254 are used by utilities like l2ping, etc.
|
|
|
|
*/
|
|
|
|
|
|
|
|
spin_lock_bh(&conn->lock);
|
|
|
|
|
|
|
|
if (++conn->tx_ident > 128)
|
|
|
|
conn->tx_ident = 1;
|
|
|
|
|
|
|
|
id = conn->tx_ident;
|
|
|
|
|
|
|
|
spin_unlock_bh(&conn->lock);
|
|
|
|
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2011-04-28 22:55:53 +02:00
|
|
|
static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len, void *data)
|
2007-10-20 13:37:56 +02:00
|
|
|
{
|
|
|
|
struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data);
|
2011-01-03 10:14:36 +01:00
|
|
|
u8 flags;
|
2007-10-20 13:37:56 +02:00
|
|
|
|
|
|
|
BT_DBG("code 0x%2.2x", code);
|
|
|
|
|
|
|
|
if (!skb)
|
2010-05-01 21:15:43 +02:00
|
|
|
return;
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2011-01-03 10:14:36 +01:00
|
|
|
if (lmp_no_flush_capable(conn->hcon->hdev))
|
|
|
|
flags = ACL_START_NO_FLUSH;
|
|
|
|
else
|
|
|
|
flags = ACL_START;
|
|
|
|
|
2011-05-24 03:06:04 +02:00
|
|
|
bt_cb(skb)->force_active = BT_POWER_FORCE_ACTIVE_ON;
|
2011-11-01 09:58:57 +01:00
|
|
|
skb->priority = HCI_PRIO_MAX;
|
2011-05-24 03:06:04 +02:00
|
|
|
|
2011-11-02 14:52:01 +01:00
|
|
|
hci_send_acl(conn->hchan, skb, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_do_send(struct l2cap_chan *chan, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct hci_conn *hcon = chan->conn->hcon;
|
|
|
|
u16 flags;
|
|
|
|
|
|
|
|
BT_DBG("chan %p, skb %p len %d priority %u", chan, skb, skb->len,
|
|
|
|
skb->priority);
|
|
|
|
|
|
|
|
if (!test_bit(FLAG_FLUSHABLE, &chan->flags) &&
|
|
|
|
lmp_no_flush_capable(hcon->hdev))
|
|
|
|
flags = ACL_START_NO_FLUSH;
|
|
|
|
else
|
|
|
|
flags = ACL_START;
|
2011-05-24 03:06:04 +02:00
|
|
|
|
2011-11-02 14:52:01 +01:00
|
|
|
bt_cb(skb)->force_active = test_bit(FLAG_FORCE_ACTIVE, &chan->flags);
|
|
|
|
hci_send_acl(chan->conn->hchan, skb, flags);
|
2007-10-20 13:37:56 +02:00
|
|
|
}
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
static inline void l2cap_send_sframe(struct l2cap_chan *chan, u32 control)
|
2009-08-21 03:25:57 +02:00
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct l2cap_hdr *lh;
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_conn *conn = chan->conn;
|
2011-10-11 12:37:52 +02:00
|
|
|
int count, hlen;
|
2009-08-21 03:26:02 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (chan->state != BT_CONNECTED)
|
2010-05-14 01:50:12 +02:00
|
|
|
return;
|
|
|
|
|
2011-10-11 12:37:52 +02:00
|
|
|
if (test_bit(FLAG_EXT_CTRL, &chan->flags))
|
|
|
|
hlen = L2CAP_EXT_HDR_SIZE;
|
|
|
|
else
|
|
|
|
hlen = L2CAP_ENH_HDR_SIZE;
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16)
|
2011-10-17 11:19:58 +02:00
|
|
|
hlen += L2CAP_FCS_SIZE;
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
BT_DBG("chan %p, control 0x%8.8x", chan, control);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2009-08-21 03:26:02 +02:00
|
|
|
count = min_t(unsigned int, conn->mtu, hlen);
|
2011-10-11 12:37:48 +02:00
|
|
|
|
|
|
|
control |= __set_sframe(chan);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state))
|
2011-10-11 12:37:49 +02:00
|
|
|
control |= __set_ctrl_final(chan);
|
2010-05-01 21:15:37 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_and_clear_bit(CONN_SEND_PBIT, &chan->conn_state))
|
2011-10-11 12:37:50 +02:00
|
|
|
control |= __set_ctrl_poll(chan);
|
2010-05-01 21:15:37 +02:00
|
|
|
|
2009-08-21 03:25:57 +02:00
|
|
|
skb = bt_skb_alloc(count, GFP_ATOMIC);
|
|
|
|
if (!skb)
|
2010-05-01 21:15:43 +02:00
|
|
|
return;
|
2009-08-21 03:25:57 +02:00
|
|
|
|
|
|
|
lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
|
2009-08-21 03:26:02 +02:00
|
|
|
lh->len = cpu_to_le16(hlen - L2CAP_HDR_SIZE);
|
2011-04-14 00:50:45 +02:00
|
|
|
lh->cid = cpu_to_le16(chan->dcid);
|
2011-10-17 11:19:56 +02:00
|
|
|
|
|
|
|
__put_control(chan, control, skb_put(skb, __ctrl_size(chan)));
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16) {
|
2011-10-17 11:19:58 +02:00
|
|
|
u16 fcs = crc16(0, (u8 *)lh, count - L2CAP_FCS_SIZE);
|
|
|
|
put_unaligned_le16(fcs, skb_put(skb, L2CAP_FCS_SIZE));
|
2009-08-21 03:26:02 +02:00
|
|
|
}
|
|
|
|
|
2011-11-02 14:52:01 +01:00
|
|
|
skb->priority = HCI_PRIO_MAX;
|
|
|
|
l2cap_do_send(chan, skb);
|
2009-08-21 03:25:57 +02:00
|
|
|
}
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
static inline void l2cap_send_rr_or_rnr(struct l2cap_chan *chan, u32 control)
|
2009-08-26 09:04:03 +02:00
|
|
|
{
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) {
|
2011-10-11 12:37:44 +02:00
|
|
|
control |= __set_ctrl_super(chan, L2CAP_SUPER_RNR);
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_RNR_SENT, &chan->conn_state);
|
2010-05-01 21:15:44 +02:00
|
|
|
} else
|
2011-10-11 12:37:44 +02:00
|
|
|
control |= __set_ctrl_super(chan, L2CAP_SUPER_RR);
|
2009-08-26 09:04:03 +02:00
|
|
|
|
2011-10-11 12:37:46 +02:00
|
|
|
control |= __set_reqseq(chan, chan->buffer_seq);
|
2009-10-03 07:34:40 +02:00
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_sframe(chan, control);
|
2009-08-26 09:04:03 +02:00
|
|
|
}
|
|
|
|
|
2011-04-12 23:15:09 +02:00
|
|
|
static inline int __l2cap_no_conn_pending(struct l2cap_chan *chan)
|
2010-07-08 11:14:41 +02:00
|
|
|
{
|
2011-06-10 22:02:12 +02:00
|
|
|
return !test_bit(CONF_CONNECT_PEND, &chan->conf_state);
|
2010-07-08 11:14:41 +02:00
|
|
|
}
|
|
|
|
|
2011-03-25 17:59:37 +01:00
|
|
|
static void l2cap_do_start(struct l2cap_chan *chan)
|
2008-07-14 20:13:44 +02:00
|
|
|
{
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_conn *conn = chan->conn;
|
2008-07-14 20:13:44 +02:00
|
|
|
|
|
|
|
if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) {
|
2009-02-06 23:35:19 +01:00
|
|
|
if (!(conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE))
|
|
|
|
return;
|
|
|
|
|
2011-11-05 22:54:24 +01:00
|
|
|
if (l2cap_chan_check_security(chan) &&
|
2011-04-12 23:31:57 +02:00
|
|
|
__l2cap_no_conn_pending(chan)) {
|
2008-07-14 20:13:54 +02:00
|
|
|
struct l2cap_conn_req req;
|
2011-04-14 00:50:45 +02:00
|
|
|
req.scid = cpu_to_le16(chan->scid);
|
|
|
|
req.psm = chan->psm;
|
2008-07-14 20:13:44 +02:00
|
|
|
|
2011-03-25 17:59:37 +01:00
|
|
|
chan->ident = l2cap_get_ident(conn);
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_CONNECT_PEND, &chan->conf_state);
|
2008-07-14 20:13:44 +02:00
|
|
|
|
2011-03-25 17:59:37 +01:00
|
|
|
l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_REQ,
|
|
|
|
sizeof(req), &req);
|
2008-07-14 20:13:54 +02:00
|
|
|
}
|
2008-07-14 20:13:44 +02:00
|
|
|
} else {
|
|
|
|
struct l2cap_info_req req;
|
|
|
|
req.type = cpu_to_le16(L2CAP_IT_FEAT_MASK);
|
|
|
|
|
|
|
|
conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_SENT;
|
|
|
|
conn->info_ident = l2cap_get_ident(conn);
|
|
|
|
|
|
|
|
mod_timer(&conn->info_timer, jiffies +
|
|
|
|
msecs_to_jiffies(L2CAP_INFO_TIMEOUT));
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, conn->info_ident,
|
|
|
|
L2CAP_INFO_REQ, sizeof(req), &req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-08 01:54:45 +02:00
|
|
|
static inline int l2cap_mode_supported(__u8 mode, __u32 feat_mask)
|
|
|
|
{
|
|
|
|
u32 local_feat_mask = l2cap_feat_mask;
|
2010-07-18 21:25:54 +02:00
|
|
|
if (!disable_ertm)
|
2010-06-08 01:54:45 +02:00
|
|
|
local_feat_mask |= L2CAP_FEAT_ERTM | L2CAP_FEAT_STREAMING;
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case L2CAP_MODE_ERTM:
|
|
|
|
return L2CAP_FEAT_ERTM & feat_mask & local_feat_mask;
|
|
|
|
case L2CAP_MODE_STREAMING:
|
|
|
|
return L2CAP_FEAT_STREAMING & feat_mask & local_feat_mask;
|
|
|
|
default:
|
|
|
|
return 0x00;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-28 22:55:53 +02:00
|
|
|
static void l2cap_send_disconn_req(struct l2cap_conn *conn, struct l2cap_chan *chan, int err)
|
2009-07-23 15:27:23 +02:00
|
|
|
{
|
2011-04-01 05:53:45 +02:00
|
|
|
struct sock *sk;
|
2009-07-23 15:27:23 +02:00
|
|
|
struct l2cap_disconn_req req;
|
|
|
|
|
2010-05-14 01:50:12 +02:00
|
|
|
if (!conn)
|
|
|
|
return;
|
|
|
|
|
2011-04-01 05:53:45 +02:00
|
|
|
sk = chan->sk;
|
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->mode == L2CAP_MODE_ERTM) {
|
2011-05-17 20:13:19 +02:00
|
|
|
__clear_retrans_timer(chan);
|
|
|
|
__clear_monitor_timer(chan);
|
|
|
|
__clear_ack_timer(chan);
|
2010-05-14 01:50:12 +02:00
|
|
|
}
|
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
req.dcid = cpu_to_le16(chan->dcid);
|
|
|
|
req.scid = cpu_to_le16(chan->scid);
|
2009-07-23 15:27:23 +02:00
|
|
|
l2cap_send_cmd(conn, l2cap_get_ident(conn),
|
|
|
|
L2CAP_DISCONN_REQ, sizeof(req), &req);
|
2010-05-14 01:50:12 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_DISCONN);
|
2010-05-20 21:21:53 +02:00
|
|
|
sk->sk_err = err;
|
2009-07-23 15:27:23 +02:00
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* ---- L2CAP connections ---- */
|
2007-10-20 13:37:56 +02:00
|
|
|
static void l2cap_conn_start(struct l2cap_conn *conn)
|
|
|
|
{
|
2011-04-01 05:35:21 +02:00
|
|
|
struct l2cap_chan *chan, *tmp;
|
2007-10-20 13:37:56 +02:00
|
|
|
|
|
|
|
BT_DBG("conn %p", conn);
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
read_lock(&conn->chan_lock);
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2011-04-01 05:35:21 +02:00
|
|
|
list_for_each_entry_safe(chan, tmp, &conn->chan_l, list) {
|
2011-03-25 04:22:30 +01:00
|
|
|
struct sock *sk = chan->sk;
|
2011-03-31 21:17:41 +02:00
|
|
|
|
2007-10-20 13:37:56 +02:00
|
|
|
bh_lock_sock(sk);
|
|
|
|
|
2011-05-02 22:13:55 +02:00
|
|
|
if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) {
|
2008-07-14 20:13:44 +02:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (chan->state == BT_CONNECT) {
|
2010-07-09 21:38:35 +02:00
|
|
|
struct l2cap_conn_req req;
|
2008-07-14 20:13:44 +02:00
|
|
|
|
2011-11-05 22:54:24 +01:00
|
|
|
if (!l2cap_chan_check_security(chan) ||
|
2011-04-12 23:15:09 +02:00
|
|
|
!__l2cap_no_conn_pending(chan)) {
|
2010-07-09 21:38:35 +02:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
continue;
|
|
|
|
}
|
2008-07-14 20:13:44 +02:00
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (!l2cap_mode_supported(chan->mode, conn->feat_mask)
|
|
|
|
&& test_bit(CONF_STATE2_DEVICE,
|
|
|
|
&chan->conf_state)) {
|
2011-05-05 00:42:50 +02:00
|
|
|
/* l2cap_chan_close() calls list_del(chan)
|
2011-04-01 05:35:21 +02:00
|
|
|
* so release the lock */
|
2011-06-17 17:57:25 +02:00
|
|
|
read_unlock(&conn->chan_lock);
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_chan_close(chan, ECONNRESET);
|
2011-06-17 17:57:25 +02:00
|
|
|
read_lock(&conn->chan_lock);
|
2010-07-09 21:38:35 +02:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
continue;
|
2008-07-14 20:13:54 +02:00
|
|
|
}
|
2010-07-09 21:38:35 +02:00
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
req.scid = cpu_to_le16(chan->scid);
|
|
|
|
req.psm = chan->psm;
|
2010-07-09 21:38:35 +02:00
|
|
|
|
2011-03-25 17:59:37 +01:00
|
|
|
chan->ident = l2cap_get_ident(conn);
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_CONNECT_PEND, &chan->conf_state);
|
2010-07-09 21:38:35 +02:00
|
|
|
|
2011-03-25 17:59:37 +01:00
|
|
|
l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_REQ,
|
|
|
|
sizeof(req), &req);
|
2010-07-09 21:38:35 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
} else if (chan->state == BT_CONNECT2) {
|
2008-07-14 20:13:44 +02:00
|
|
|
struct l2cap_conn_rsp rsp;
|
2010-07-09 01:08:18 +02:00
|
|
|
char buf[128];
|
2011-04-14 00:50:45 +02:00
|
|
|
rsp.scid = cpu_to_le16(chan->dcid);
|
|
|
|
rsp.dcid = cpu_to_le16(chan->scid);
|
2008-07-14 20:13:44 +02:00
|
|
|
|
2011-11-05 22:54:24 +01:00
|
|
|
if (l2cap_chan_check_security(chan)) {
|
2009-01-15 21:57:00 +01:00
|
|
|
if (bt_sk(sk)->defer_setup) {
|
|
|
|
struct sock *parent = bt_sk(sk)->parent;
|
|
|
|
rsp.result = cpu_to_le16(L2CAP_CR_PEND);
|
|
|
|
rsp.status = cpu_to_le16(L2CAP_CS_AUTHOR_PEND);
|
2011-07-15 20:30:21 +02:00
|
|
|
if (parent)
|
|
|
|
parent->sk_data_ready(parent, 0);
|
2009-01-15 21:57:00 +01:00
|
|
|
|
|
|
|
} else {
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONFIG);
|
2009-01-15 21:57:00 +01:00
|
|
|
rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS);
|
|
|
|
rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO);
|
|
|
|
}
|
2008-07-14 20:13:44 +02:00
|
|
|
} else {
|
|
|
|
rsp.result = cpu_to_le16(L2CAP_CR_PEND);
|
|
|
|
rsp.status = cpu_to_le16(L2CAP_CS_AUTHEN_PEND);
|
|
|
|
}
|
|
|
|
|
2011-03-25 17:59:37 +01:00
|
|
|
l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_RSP,
|
|
|
|
sizeof(rsp), &rsp);
|
2010-07-09 01:08:18 +02:00
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (test_bit(CONF_REQ_SENT, &chan->conf_state) ||
|
2010-07-09 01:08:18 +02:00
|
|
|
rsp.result != L2CAP_CR_SUCCESS) {
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_REQ_SENT, &chan->conf_state);
|
2010-07-09 01:08:18 +02:00
|
|
|
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
|
2011-03-25 18:16:54 +01:00
|
|
|
l2cap_build_conf_req(chan, buf), buf);
|
|
|
|
chan->num_conf_req++;
|
2007-10-20 13:37:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
read_unlock(&conn->chan_lock);
|
2007-10-20 13:37:56 +02:00
|
|
|
}
|
|
|
|
|
2011-02-11 02:38:50 +01:00
|
|
|
/* Find socket with cid and source bdaddr.
|
|
|
|
* Returns closest match, locked.
|
|
|
|
*/
|
2011-04-27 23:26:32 +02:00
|
|
|
static struct l2cap_chan *l2cap_global_chan_by_scid(int state, __le16 cid, bdaddr_t *src)
|
2011-02-11 02:38:50 +01:00
|
|
|
{
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *c, *c1 = NULL;
|
2011-02-11 02:38:50 +01:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
read_lock(&chan_list_lock);
|
2011-02-11 02:38:50 +01:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
list_for_each_entry(c, &chan_list, global_l) {
|
|
|
|
struct sock *sk = c->sk;
|
2011-04-14 00:50:45 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (state && c->state != state)
|
2011-02-11 02:38:50 +01:00
|
|
|
continue;
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
if (c->scid == cid) {
|
2011-02-11 02:38:50 +01:00
|
|
|
/* Exact match. */
|
2011-04-27 23:26:32 +02:00
|
|
|
if (!bacmp(&bt_sk(sk)->src, src)) {
|
|
|
|
read_unlock(&chan_list_lock);
|
|
|
|
return c;
|
|
|
|
}
|
2011-02-11 02:38:50 +01:00
|
|
|
|
|
|
|
/* Closest match */
|
|
|
|
if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY))
|
2011-04-27 23:26:32 +02:00
|
|
|
c1 = c;
|
2011-02-11 02:38:50 +01:00
|
|
|
}
|
|
|
|
}
|
2011-04-14 00:01:22 +02:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
read_unlock(&chan_list_lock);
|
2011-02-11 02:38:50 +01:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
return c1;
|
2011-02-11 02:38:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_le_conn_ready(struct l2cap_conn *conn)
|
|
|
|
{
|
2011-04-04 21:00:55 +02:00
|
|
|
struct sock *parent, *sk;
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *chan, *pchan;
|
2011-02-11 02:38:50 +01:00
|
|
|
|
|
|
|
BT_DBG("");
|
|
|
|
|
|
|
|
/* Check if we have socket listening on cid */
|
2011-04-27 23:26:32 +02:00
|
|
|
pchan = l2cap_global_chan_by_scid(BT_LISTEN, L2CAP_CID_LE_DATA,
|
2011-02-11 02:38:50 +01:00
|
|
|
conn->src);
|
2011-04-27 23:26:32 +02:00
|
|
|
if (!pchan)
|
2011-02-11 02:38:50 +01:00
|
|
|
return;
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
parent = pchan->sk;
|
|
|
|
|
2011-04-14 23:34:34 +02:00
|
|
|
bh_lock_sock(parent);
|
|
|
|
|
2011-02-11 02:38:50 +01:00
|
|
|
/* Check for backlog size */
|
|
|
|
if (sk_acceptq_is_full(parent)) {
|
|
|
|
BT_DBG("backlog full %d", parent->sk_ack_backlog);
|
|
|
|
goto clean;
|
|
|
|
}
|
|
|
|
|
2011-05-16 22:24:37 +02:00
|
|
|
chan = pchan->ops->new_connection(pchan->data);
|
|
|
|
if (!chan)
|
2011-02-11 02:38:50 +01:00
|
|
|
goto clean;
|
|
|
|
|
2011-05-16 22:24:37 +02:00
|
|
|
sk = chan->sk;
|
2011-04-08 20:40:02 +02:00
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
write_lock_bh(&conn->chan_lock);
|
2011-02-11 02:38:50 +01:00
|
|
|
|
|
|
|
hci_conn_hold(conn->hcon);
|
|
|
|
|
|
|
|
bacpy(&bt_sk(sk)->src, conn->src);
|
|
|
|
bacpy(&bt_sk(sk)->dst, conn->dst);
|
|
|
|
|
2011-03-25 04:39:48 +01:00
|
|
|
bt_accept_enqueue(parent, sk);
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
__l2cap_chan_add(conn, chan);
|
|
|
|
|
2011-05-17 19:59:01 +02:00
|
|
|
__set_chan_timer(chan, sk->sk_sndtimeo);
|
2011-02-11 02:38:50 +01:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECTED);
|
2011-02-11 02:38:50 +01:00
|
|
|
parent->sk_data_ready(parent, 0);
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
write_unlock_bh(&conn->chan_lock);
|
2011-02-11 02:38:50 +01:00
|
|
|
|
|
|
|
clean:
|
|
|
|
bh_unlock_sock(parent);
|
|
|
|
}
|
|
|
|
|
2011-01-27 01:42:57 +01:00
|
|
|
static void l2cap_chan_ready(struct sock *sk)
|
|
|
|
{
|
|
|
|
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
|
|
|
|
struct sock *parent = bt_sk(sk)->parent;
|
|
|
|
|
|
|
|
BT_DBG("sk %p, parent %p", sk, parent);
|
|
|
|
|
|
|
|
chan->conf_state = 0;
|
|
|
|
__clear_chan_timer(chan);
|
|
|
|
|
2011-06-20 23:53:18 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECTED);
|
2011-01-27 01:42:57 +01:00
|
|
|
sk->sk_state_change(sk);
|
|
|
|
|
|
|
|
if (parent)
|
|
|
|
parent->sk_data_ready(parent, 0);
|
|
|
|
}
|
|
|
|
|
2007-10-20 13:37:56 +02:00
|
|
|
static void l2cap_conn_ready(struct l2cap_conn *conn)
|
|
|
|
{
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2008-07-14 20:13:44 +02:00
|
|
|
BT_DBG("conn %p", conn);
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2011-02-11 02:38:50 +01:00
|
|
|
if (!conn->hcon->out && conn->hcon->type == LE_LINK)
|
|
|
|
l2cap_le_conn_ready(conn);
|
|
|
|
|
2011-08-20 02:06:55 +02:00
|
|
|
if (conn->hcon->out && conn->hcon->type == LE_LINK)
|
|
|
|
smp_conn_security(conn, conn->hcon->pending_sec_level);
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
read_lock(&conn->chan_lock);
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
list_for_each_entry(chan, &conn->chan_l, list) {
|
2011-03-25 04:22:30 +01:00
|
|
|
struct sock *sk = chan->sk;
|
2011-03-31 21:17:41 +02:00
|
|
|
|
2008-07-14 20:13:44 +02:00
|
|
|
bh_lock_sock(sk);
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2011-06-18 03:46:26 +02:00
|
|
|
if (conn->hcon->type == LE_LINK) {
|
2011-06-07 23:46:31 +02:00
|
|
|
if (smp_conn_security(conn, chan->sec_level))
|
2011-01-27 01:42:57 +01:00
|
|
|
l2cap_chan_ready(sk);
|
2011-02-11 02:38:49 +01:00
|
|
|
|
2011-06-18 03:46:26 +02:00
|
|
|
} else if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) {
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECTED);
|
2008-07-14 20:13:44 +02:00
|
|
|
sk->sk_state_change(sk);
|
2011-06-07 23:46:31 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
} else if (chan->state == BT_CONNECT)
|
2011-03-25 17:59:37 +01:00
|
|
|
l2cap_do_start(chan);
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2008-07-14 20:13:44 +02:00
|
|
|
bh_unlock_sock(sk);
|
2007-10-20 13:37:56 +02:00
|
|
|
}
|
2008-07-14 20:13:44 +02:00
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
read_unlock(&conn->chan_lock);
|
2007-10-20 13:37:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Notify sockets that we cannot guaranty reliability anymore */
|
|
|
|
static void l2cap_conn_unreliable(struct l2cap_conn *conn, int err)
|
|
|
|
{
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2007-10-20 13:37:56 +02:00
|
|
|
|
|
|
|
BT_DBG("conn %p", conn);
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
read_lock(&conn->chan_lock);
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
list_for_each_entry(chan, &conn->chan_l, list) {
|
2011-03-25 04:22:30 +01:00
|
|
|
struct sock *sk = chan->sk;
|
2011-03-31 21:17:41 +02:00
|
|
|
|
2011-10-11 13:04:32 +02:00
|
|
|
if (test_bit(FLAG_FORCE_RELIABLE, &chan->flags))
|
2007-10-20 13:37:56 +02:00
|
|
|
sk->sk_err = err;
|
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
read_unlock(&conn->chan_lock);
|
2007-10-20 13:37:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_info_timeout(unsigned long arg)
|
|
|
|
{
|
|
|
|
struct l2cap_conn *conn = (void *) arg;
|
|
|
|
|
2009-02-06 23:35:19 +01:00
|
|
|
conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE;
|
2009-02-09 09:18:02 +01:00
|
|
|
conn->info_ident = 0;
|
2009-02-06 23:35:19 +01:00
|
|
|
|
2007-10-20 13:37:56 +02:00
|
|
|
l2cap_conn_start(conn);
|
|
|
|
}
|
|
|
|
|
2011-06-14 18:37:41 +02:00
|
|
|
static void l2cap_conn_del(struct hci_conn *hcon, int err)
|
|
|
|
{
|
|
|
|
struct l2cap_conn *conn = hcon->l2cap_data;
|
|
|
|
struct l2cap_chan *chan, *l;
|
|
|
|
struct sock *sk;
|
|
|
|
|
|
|
|
if (!conn)
|
|
|
|
return;
|
|
|
|
|
|
|
|
BT_DBG("hcon %p conn %p, err %d", hcon, conn, err);
|
|
|
|
|
|
|
|
kfree_skb(conn->rx_skb);
|
|
|
|
|
|
|
|
/* Kill channels */
|
|
|
|
list_for_each_entry_safe(chan, l, &conn->chan_l, list) {
|
|
|
|
sk = chan->sk;
|
|
|
|
bh_lock_sock(sk);
|
|
|
|
l2cap_chan_del(chan, err);
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
chan->ops->close(chan->data);
|
|
|
|
}
|
|
|
|
|
2011-11-02 14:52:01 +01:00
|
|
|
hci_chan_del(conn->hchan);
|
|
|
|
|
2011-06-14 18:37:41 +02:00
|
|
|
if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT)
|
|
|
|
del_timer_sync(&conn->info_timer);
|
|
|
|
|
2011-08-20 02:06:51 +02:00
|
|
|
if (test_and_clear_bit(HCI_CONN_LE_SMP_PEND, &hcon->pend)) {
|
2011-06-14 18:37:41 +02:00
|
|
|
del_timer(&conn->security_timer);
|
2011-09-05 19:31:31 +02:00
|
|
|
smp_chan_destroy(conn);
|
2011-08-20 02:06:51 +02:00
|
|
|
}
|
2011-06-14 18:37:41 +02:00
|
|
|
|
|
|
|
hcon->l2cap_data = NULL;
|
|
|
|
kfree(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void security_timeout(unsigned long arg)
|
|
|
|
{
|
|
|
|
struct l2cap_conn *conn = (void *) arg;
|
|
|
|
|
|
|
|
l2cap_conn_del(conn->hcon, ETIMEDOUT);
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status)
|
|
|
|
{
|
2006-07-03 10:02:46 +02:00
|
|
|
struct l2cap_conn *conn = hcon->l2cap_data;
|
2011-11-02 14:52:01 +01:00
|
|
|
struct hci_chan *hchan;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-07-03 10:02:46 +02:00
|
|
|
if (conn || status)
|
2005-04-17 00:20:36 +02:00
|
|
|
return conn;
|
|
|
|
|
2011-11-02 14:52:01 +01:00
|
|
|
hchan = hci_chan_create(hcon);
|
|
|
|
if (!hchan)
|
|
|
|
return NULL;
|
|
|
|
|
2006-07-03 10:02:46 +02:00
|
|
|
conn = kzalloc(sizeof(struct l2cap_conn), GFP_ATOMIC);
|
2011-11-02 14:52:01 +01:00
|
|
|
if (!conn) {
|
|
|
|
hci_chan_del(hchan);
|
2005-04-17 00:20:36 +02:00
|
|
|
return NULL;
|
2011-11-02 14:52:01 +01:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
hcon->l2cap_data = conn;
|
|
|
|
conn->hcon = hcon;
|
2011-11-02 14:52:01 +01:00
|
|
|
conn->hchan = hchan;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-11-02 14:52:01 +01:00
|
|
|
BT_DBG("hcon %p conn %p hchan %p", hcon, conn, hchan);
|
2006-07-03 10:02:46 +02:00
|
|
|
|
2011-02-11 02:38:49 +01:00
|
|
|
if (hcon->hdev->le_mtu && hcon->type == LE_LINK)
|
|
|
|
conn->mtu = hcon->hdev->le_mtu;
|
|
|
|
else
|
|
|
|
conn->mtu = hcon->hdev->acl_mtu;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
conn->src = &hcon->hdev->bdaddr;
|
|
|
|
conn->dst = &hcon->dst;
|
|
|
|
|
2007-10-20 13:37:56 +02:00
|
|
|
conn->feat_mask = 0;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
spin_lock_init(&conn->lock);
|
2011-03-31 21:17:41 +02:00
|
|
|
rwlock_init(&conn->chan_lock);
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&conn->chan_l);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-06-14 18:37:41 +02:00
|
|
|
if (hcon->type == LE_LINK)
|
|
|
|
setup_timer(&conn->security_timer, security_timeout,
|
|
|
|
(unsigned long) conn);
|
|
|
|
else
|
2011-02-11 02:38:50 +01:00
|
|
|
setup_timer(&conn->info_timer, l2cap_info_timeout,
|
2009-10-18 22:28:30 +02:00
|
|
|
(unsigned long) conn);
|
|
|
|
|
2011-11-07 13:20:25 +01:00
|
|
|
conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM;
|
2009-02-12 14:02:50 +01:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
return conn;
|
|
|
|
}
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
static inline void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2011-03-31 21:17:41 +02:00
|
|
|
write_lock_bh(&conn->chan_lock);
|
2011-03-25 04:22:30 +01:00
|
|
|
__l2cap_chan_add(conn, chan);
|
2011-03-31 21:17:41 +02:00
|
|
|
write_unlock_bh(&conn->chan_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ---- Socket interface ---- */
|
|
|
|
|
|
|
|
/* Find socket with psm and source bdaddr.
|
|
|
|
* Returns closest match.
|
|
|
|
*/
|
2011-04-27 23:26:32 +02:00
|
|
|
static struct l2cap_chan *l2cap_global_chan_by_psm(int state, __le16 psm, bdaddr_t *src)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *c, *c1 = NULL;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
read_lock(&chan_list_lock);
|
2010-11-01 19:43:53 +01:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
list_for_each_entry(c, &chan_list, global_l) {
|
|
|
|
struct sock *sk = c->sk;
|
2011-04-14 00:50:45 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (state && c->state != state)
|
2005-04-17 00:20:36 +02:00
|
|
|
continue;
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
if (c->psm == psm) {
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Exact match. */
|
2011-04-27 23:26:32 +02:00
|
|
|
if (!bacmp(&bt_sk(sk)->src, src)) {
|
2011-06-01 08:29:54 +02:00
|
|
|
read_unlock(&chan_list_lock);
|
2011-04-27 23:26:32 +02:00
|
|
|
return c;
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Closest match */
|
|
|
|
if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY))
|
2011-04-27 23:26:32 +02:00
|
|
|
c1 = c;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
read_unlock(&chan_list_lock);
|
2010-11-01 19:43:53 +01:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
return c1;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2011-04-12 23:17:14 +02:00
|
|
|
int l2cap_chan_connect(struct l2cap_chan *chan)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2011-04-08 20:40:02 +02:00
|
|
|
struct sock *sk = chan->sk;
|
2005-04-17 00:20:36 +02:00
|
|
|
bdaddr_t *src = &bt_sk(sk)->src;
|
|
|
|
bdaddr_t *dst = &bt_sk(sk)->dst;
|
|
|
|
struct l2cap_conn *conn;
|
|
|
|
struct hci_conn *hcon;
|
|
|
|
struct hci_dev *hdev;
|
2008-09-09 07:19:20 +02:00
|
|
|
__u8 auth_type;
|
2009-04-20 07:09:16 +02:00
|
|
|
int err;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2009-02-12 05:07:45 +01:00
|
|
|
BT_DBG("%s -> %s psm 0x%2.2x", batostr(src), batostr(dst),
|
2011-04-14 00:50:45 +02:00
|
|
|
chan->psm);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2009-04-20 06:31:08 +02:00
|
|
|
hdev = hci_get_route(dst, src);
|
|
|
|
if (!hdev)
|
2005-04-17 00:20:36 +02:00
|
|
|
return -EHOSTUNREACH;
|
|
|
|
|
|
|
|
hci_dev_lock_bh(hdev);
|
|
|
|
|
2011-04-12 23:31:57 +02:00
|
|
|
auth_type = l2cap_get_auth_type(chan);
|
2008-09-09 07:19:20 +02:00
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
if (chan->dcid == L2CAP_CID_LE_DATA)
|
2011-02-11 02:38:49 +01:00
|
|
|
hcon = hci_connect(hdev, LE_LINK, dst,
|
2011-04-12 23:31:57 +02:00
|
|
|
chan->sec_level, auth_type);
|
2011-02-11 02:38:49 +01:00
|
|
|
else
|
|
|
|
hcon = hci_connect(hdev, ACL_LINK, dst,
|
2011-04-12 23:31:57 +02:00
|
|
|
chan->sec_level, auth_type);
|
2011-02-11 02:38:49 +01:00
|
|
|
|
2011-02-22 20:10:53 +01:00
|
|
|
if (IS_ERR(hcon)) {
|
|
|
|
err = PTR_ERR(hcon);
|
2005-04-17 00:20:36 +02:00
|
|
|
goto done;
|
2011-02-22 20:10:53 +01:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
conn = l2cap_conn_add(hcon, 0);
|
|
|
|
if (!conn) {
|
|
|
|
hci_conn_put(hcon);
|
2011-02-22 20:10:53 +01:00
|
|
|
err = -ENOMEM;
|
2005-04-17 00:20:36 +02:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Update source addr of the socket */
|
|
|
|
bacpy(src, conn->src);
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
l2cap_chan_add(conn, chan);
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECT);
|
2011-05-17 19:59:01 +02:00
|
|
|
__set_chan_timer(chan, sk->sk_sndtimeo);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (hcon->state == BT_CONNECTED) {
|
2011-05-02 22:13:55 +02:00
|
|
|
if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) {
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2011-11-05 22:54:24 +01:00
|
|
|
if (l2cap_chan_check_security(chan))
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECTED);
|
2008-07-14 20:13:44 +02:00
|
|
|
} else
|
2011-03-25 17:59:37 +01:00
|
|
|
l2cap_do_start(chan);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2011-02-22 20:10:53 +01:00
|
|
|
err = 0;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
done:
|
|
|
|
hci_dev_unlock_bh(hdev);
|
|
|
|
hci_dev_put(hdev);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-02-04 06:08:36 +01:00
|
|
|
int __l2cap_wait_ack(struct sock *sk)
|
2010-05-01 21:15:44 +02:00
|
|
|
{
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
|
2010-05-01 21:15:44 +02:00
|
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
int err = 0;
|
|
|
|
int timeo = HZ/5;
|
|
|
|
|
2010-05-10 11:33:10 +02:00
|
|
|
add_wait_queue(sk_sleep(sk), &wait);
|
2011-07-26 00:36:26 +02:00
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
while (chan->unacked_frames > 0 && chan->conn) {
|
2010-05-01 21:15:44 +02:00
|
|
|
if (!timeo)
|
|
|
|
timeo = HZ/5;
|
|
|
|
|
|
|
|
if (signal_pending(current)) {
|
|
|
|
err = sock_intr_errno(timeo);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
release_sock(sk);
|
|
|
|
timeo = schedule_timeout(timeo);
|
|
|
|
lock_sock(sk);
|
2011-07-26 00:36:26 +02:00
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
2010-05-01 21:15:44 +02:00
|
|
|
|
|
|
|
err = sock_error(sk);
|
|
|
|
if (err)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
set_current_state(TASK_RUNNING);
|
2010-05-10 11:33:10 +02:00
|
|
|
remove_wait_queue(sk_sleep(sk), &wait);
|
2010-05-01 21:15:44 +02:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2009-08-21 03:26:00 +02:00
|
|
|
static void l2cap_monitor_timeout(unsigned long arg)
|
|
|
|
{
|
2011-03-25 23:43:39 +01:00
|
|
|
struct l2cap_chan *chan = (void *) arg;
|
|
|
|
struct sock *sk = chan->sk;
|
2009-08-21 03:26:00 +02:00
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
BT_DBG("chan %p", chan);
|
2010-04-19 19:45:38 +02:00
|
|
|
|
2009-08-24 05:45:19 +02:00
|
|
|
bh_lock_sock(sk);
|
2011-03-26 00:15:28 +01:00
|
|
|
if (chan->retry_count >= chan->remote_max_tx) {
|
2011-04-14 01:23:55 +02:00
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNABORTED);
|
2009-12-15 10:38:04 +01:00
|
|
|
bh_unlock_sock(sk);
|
2009-08-21 03:26:00 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->retry_count++;
|
2011-05-17 20:13:19 +02:00
|
|
|
__set_monitor_timer(chan);
|
2009-08-21 03:26:00 +02:00
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_rr_or_rnr(chan, L2CAP_CTRL_POLL);
|
2009-08-24 05:45:19 +02:00
|
|
|
bh_unlock_sock(sk);
|
2009-08-21 03:26:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_retrans_timeout(unsigned long arg)
|
|
|
|
{
|
2011-03-25 23:43:39 +01:00
|
|
|
struct l2cap_chan *chan = (void *) arg;
|
|
|
|
struct sock *sk = chan->sk;
|
2009-08-21 03:26:00 +02:00
|
|
|
|
2011-04-04 20:59:54 +02:00
|
|
|
BT_DBG("chan %p", chan);
|
2010-04-19 19:45:38 +02:00
|
|
|
|
2009-08-24 05:45:19 +02:00
|
|
|
bh_lock_sock(sk);
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->retry_count = 1;
|
2011-05-17 20:13:19 +02:00
|
|
|
__set_monitor_timer(chan);
|
2009-08-21 03:26:00 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_WAIT_F, &chan->conn_state);
|
2009-08-21 03:26:00 +02:00
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_rr_or_rnr(chan, L2CAP_CTRL_POLL);
|
2009-08-24 05:45:19 +02:00
|
|
|
bh_unlock_sock(sk);
|
2009-08-21 03:26:00 +02:00
|
|
|
}
|
|
|
|
|
2011-03-25 23:58:34 +01:00
|
|
|
static void l2cap_drop_acked_frames(struct l2cap_chan *chan)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2009-08-21 03:25:57 +02:00
|
|
|
struct sk_buff *skb;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
while ((skb = skb_peek(&chan->tx_q)) &&
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->unacked_frames) {
|
2011-03-25 23:58:34 +01:00
|
|
|
if (bt_cb(skb)->tx_seq == chan->expected_ack_seq)
|
2009-08-21 03:25:57 +02:00
|
|
|
break;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
skb = skb_dequeue(&chan->tx_q);
|
2009-08-21 03:25:57 +02:00
|
|
|
kfree_skb(skb);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->unacked_frames--;
|
2009-08-21 03:25:57 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-01 05:38:50 +02:00
|
|
|
if (!chan->unacked_frames)
|
2011-05-17 20:13:19 +02:00
|
|
|
__clear_retrans_timer(chan);
|
2009-08-21 03:25:57 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-07-28 16:24:33 +02:00
|
|
|
static void l2cap_streaming_send(struct l2cap_chan *chan)
|
2009-08-21 03:26:01 +02:00
|
|
|
{
|
2010-08-30 23:44:44 +02:00
|
|
|
struct sk_buff *skb;
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
|
|
|
u16 fcs;
|
2009-08-21 03:26:01 +02:00
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
while ((skb = skb_dequeue(&chan->tx_q))) {
|
2011-10-17 11:19:56 +02:00
|
|
|
control = __get_control(chan, skb->data + L2CAP_HDR_SIZE);
|
2011-10-11 12:37:47 +02:00
|
|
|
control |= __set_txseq(chan, chan->next_tx_seq);
|
2011-10-17 11:19:56 +02:00
|
|
|
__put_control(chan, control, skb->data + L2CAP_HDR_SIZE);
|
2009-08-21 03:26:01 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16) {
|
2011-10-17 11:19:58 +02:00
|
|
|
fcs = crc16(0, (u8 *)skb->data,
|
|
|
|
skb->len - L2CAP_FCS_SIZE);
|
|
|
|
put_unaligned_le16(fcs,
|
|
|
|
skb->data + skb->len - L2CAP_FCS_SIZE);
|
2009-08-21 03:26:02 +02:00
|
|
|
}
|
|
|
|
|
2011-04-12 23:31:57 +02:00
|
|
|
l2cap_do_send(chan, skb);
|
2009-08-21 03:26:01 +02:00
|
|
|
|
2011-10-17 11:19:57 +02:00
|
|
|
chan->next_tx_seq = __next_seq(chan, chan->next_tx_seq);
|
2009-08-21 03:26:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-11 12:37:47 +02:00
|
|
|
static void l2cap_retransmit_one_frame(struct l2cap_chan *chan, u16 tx_seq)
|
2009-08-21 03:26:03 +02:00
|
|
|
{
|
|
|
|
struct sk_buff *skb, *tx_skb;
|
2011-10-17 11:19:56 +02:00
|
|
|
u16 fcs;
|
|
|
|
u32 control;
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
skb = skb_peek(&chan->tx_q);
|
2010-05-01 21:15:44 +02:00
|
|
|
if (!skb)
|
|
|
|
return;
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2010-05-01 21:15:44 +02:00
|
|
|
do {
|
|
|
|
if (bt_cb(skb)->tx_seq == tx_seq)
|
2009-08-21 03:26:03 +02:00
|
|
|
break;
|
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
if (skb_queue_is_last(&chan->tx_q, skb))
|
2010-05-01 21:15:44 +02:00
|
|
|
return;
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
} while ((skb = skb_queue_next(&chan->tx_q, skb)));
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-03-26 00:15:28 +01:00
|
|
|
if (chan->remote_max_tx &&
|
|
|
|
bt_cb(skb)->retries == chan->remote_max_tx) {
|
2011-04-14 01:23:55 +02:00
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNABORTED);
|
2010-05-01 21:15:44 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tx_skb = skb_clone(skb, GFP_ATOMIC);
|
|
|
|
bt_cb(skb)->retries++;
|
2011-10-17 11:19:56 +02:00
|
|
|
|
|
|
|
control = __get_control(chan, tx_skb->data + L2CAP_HDR_SIZE);
|
2011-10-11 12:37:45 +02:00
|
|
|
control &= __get_sar_mask(chan);
|
2010-05-29 07:24:35 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state))
|
2011-10-11 12:37:49 +02:00
|
|
|
control |= __set_ctrl_final(chan);
|
2010-06-19 01:37:33 +02:00
|
|
|
|
2011-10-11 12:37:46 +02:00
|
|
|
control |= __set_reqseq(chan, chan->buffer_seq);
|
2011-10-11 12:37:47 +02:00
|
|
|
control |= __set_txseq(chan, tx_seq);
|
2010-05-29 07:24:35 +02:00
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
__put_control(chan, control, tx_skb->data + L2CAP_HDR_SIZE);
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16) {
|
2011-10-17 11:19:58 +02:00
|
|
|
fcs = crc16(0, (u8 *)tx_skb->data,
|
|
|
|
tx_skb->len - L2CAP_FCS_SIZE);
|
|
|
|
put_unaligned_le16(fcs,
|
|
|
|
tx_skb->data + tx_skb->len - L2CAP_FCS_SIZE);
|
2010-05-01 21:15:44 +02:00
|
|
|
}
|
|
|
|
|
2011-04-12 23:31:57 +02:00
|
|
|
l2cap_do_send(chan, tx_skb);
|
2009-08-21 03:26:03 +02:00
|
|
|
}
|
|
|
|
|
2011-07-28 16:24:33 +02:00
|
|
|
static int l2cap_ertm_send(struct l2cap_chan *chan)
|
2009-08-21 03:25:57 +02:00
|
|
|
{
|
|
|
|
struct sk_buff *skb, *tx_skb;
|
2011-10-17 11:19:56 +02:00
|
|
|
u16 fcs;
|
|
|
|
u32 control;
|
2010-05-01 21:15:43 +02:00
|
|
|
int nsent = 0;
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (chan->state != BT_CONNECTED)
|
2010-05-14 01:50:12 +02:00
|
|
|
return -ENOTCONN;
|
2009-08-21 03:26:00 +02:00
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
while ((skb = chan->tx_send_head) && (!l2cap_tx_window_full(chan))) {
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-03-26 00:15:28 +01:00
|
|
|
if (chan->remote_max_tx &&
|
|
|
|
bt_cb(skb)->retries == chan->remote_max_tx) {
|
2011-04-14 01:23:55 +02:00
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNABORTED);
|
2009-08-21 03:26:00 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-12-23 12:07:14 +01:00
|
|
|
tx_skb = skb_clone(skb, GFP_ATOMIC);
|
|
|
|
|
2009-08-21 03:26:00 +02:00
|
|
|
bt_cb(skb)->retries++;
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
control = __get_control(chan, tx_skb->data + L2CAP_HDR_SIZE);
|
2011-10-11 12:37:45 +02:00
|
|
|
control &= __get_sar_mask(chan);
|
2010-06-19 01:37:33 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state))
|
2011-10-11 12:37:49 +02:00
|
|
|
control |= __set_ctrl_final(chan);
|
2011-06-11 02:28:49 +02:00
|
|
|
|
2011-10-11 12:37:46 +02:00
|
|
|
control |= __set_reqseq(chan, chan->buffer_seq);
|
2011-10-11 12:37:47 +02:00
|
|
|
control |= __set_txseq(chan, chan->next_tx_seq);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
__put_control(chan, control, tx_skb->data + L2CAP_HDR_SIZE);
|
2009-08-21 03:26:00 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16) {
|
2011-10-17 11:19:58 +02:00
|
|
|
fcs = crc16(0, (u8 *)skb->data,
|
|
|
|
tx_skb->len - L2CAP_FCS_SIZE);
|
|
|
|
put_unaligned_le16(fcs, skb->data +
|
|
|
|
tx_skb->len - L2CAP_FCS_SIZE);
|
2009-08-21 03:26:02 +02:00
|
|
|
}
|
|
|
|
|
2011-04-12 23:31:57 +02:00
|
|
|
l2cap_do_send(chan, tx_skb);
|
2010-05-01 21:15:43 +02:00
|
|
|
|
2011-05-17 20:13:19 +02:00
|
|
|
__set_retrans_timer(chan);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-03-25 23:58:34 +01:00
|
|
|
bt_cb(skb)->tx_seq = chan->next_tx_seq;
|
2011-10-17 11:19:57 +02:00
|
|
|
|
|
|
|
chan->next_tx_seq = __next_seq(chan, chan->next_tx_seq);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-03-09 10:14:05 +01:00
|
|
|
if (bt_cb(skb)->retries == 1)
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->unacked_frames++;
|
2011-03-09 10:14:05 +01:00
|
|
|
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->frames_sent++;
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
if (skb_queue_is_last(&chan->tx_q, skb))
|
|
|
|
chan->tx_send_head = NULL;
|
2009-08-21 03:25:57 +02:00
|
|
|
else
|
2011-04-04 21:16:44 +02:00
|
|
|
chan->tx_send_head = skb_queue_next(&chan->tx_q, skb);
|
2010-05-01 21:15:37 +02:00
|
|
|
|
|
|
|
nsent++;
|
2009-08-21 03:25:57 +02:00
|
|
|
}
|
|
|
|
|
2010-05-01 21:15:37 +02:00
|
|
|
return nsent;
|
|
|
|
}
|
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
static int l2cap_retransmit_frames(struct l2cap_chan *chan)
|
2010-05-01 21:15:45 +02:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2011-04-04 21:16:44 +02:00
|
|
|
if (!skb_queue_empty(&chan->tx_q))
|
|
|
|
chan->tx_send_head = chan->tx_q.next;
|
2010-05-01 21:15:45 +02:00
|
|
|
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->next_tx_seq = chan->expected_ack_seq;
|
2011-03-25 23:43:39 +01:00
|
|
|
ret = l2cap_ertm_send(chan);
|
2010-05-01 21:15:45 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
static void l2cap_send_ack(struct l2cap_chan *chan)
|
2010-05-01 21:15:37 +02:00
|
|
|
{
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control = 0;
|
2010-05-01 21:15:37 +02:00
|
|
|
|
2011-10-11 12:37:46 +02:00
|
|
|
control |= __set_reqseq(chan, chan->buffer_seq);
|
2010-05-01 21:15:37 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) {
|
2011-10-11 12:37:44 +02:00
|
|
|
control |= __set_ctrl_super(chan, L2CAP_SUPER_RNR);
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_RNR_SENT, &chan->conn_state);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_sframe(chan, control);
|
2010-05-01 21:15:43 +02:00
|
|
|
return;
|
2010-05-01 21:15:37 +02:00
|
|
|
}
|
2010-05-01 21:15:45 +02:00
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
if (l2cap_ertm_send(chan) > 0)
|
2010-05-01 21:15:45 +02:00
|
|
|
return;
|
|
|
|
|
2011-10-11 12:37:44 +02:00
|
|
|
control |= __set_ctrl_super(chan, L2CAP_SUPER_RR);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_sframe(chan, control);
|
2009-08-21 03:25:57 +02:00
|
|
|
}
|
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
static void l2cap_send_srejtail(struct l2cap_chan *chan)
|
2010-05-01 21:15:38 +02:00
|
|
|
{
|
|
|
|
struct srej_list *tail;
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2010-05-01 21:15:38 +02:00
|
|
|
|
2011-10-11 12:37:44 +02:00
|
|
|
control = __set_ctrl_super(chan, L2CAP_SUPER_SREJ);
|
2011-10-11 12:37:49 +02:00
|
|
|
control |= __set_ctrl_final(chan);
|
2010-05-01 21:15:38 +02:00
|
|
|
|
2011-04-04 20:40:12 +02:00
|
|
|
tail = list_entry((&chan->srej_l)->prev, struct srej_list, list);
|
2011-10-11 12:37:46 +02:00
|
|
|
control |= __set_reqseq(chan, tail->tx_seq);
|
2010-05-01 21:15:38 +02:00
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_sframe(chan, control);
|
2010-05-01 21:15:38 +02:00
|
|
|
}
|
|
|
|
|
2009-08-21 03:25:57 +02:00
|
|
|
static inline int l2cap_skbuff_fromiovec(struct sock *sk, struct msghdr *msg, int len, int count, struct sk_buff *skb)
|
|
|
|
{
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_conn *conn = l2cap_pi(sk)->chan->conn;
|
2009-08-21 03:25:57 +02:00
|
|
|
struct sk_buff **frag;
|
|
|
|
int err, sent = 0;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2010-05-01 21:15:43 +02:00
|
|
|
if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count))
|
2009-08-21 03:25:57 +02:00
|
|
|
return -EFAULT;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
sent += count;
|
|
|
|
len -= count;
|
|
|
|
|
|
|
|
/* Continuation fragments (no L2CAP header) */
|
|
|
|
frag = &skb_shinfo(skb)->frag_list;
|
|
|
|
while (len) {
|
|
|
|
count = min_t(unsigned int, conn->mtu, len);
|
|
|
|
|
|
|
|
*frag = bt_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err);
|
|
|
|
if (!*frag)
|
2010-09-25 01:30:57 +02:00
|
|
|
return err;
|
2009-08-21 03:25:57 +02:00
|
|
|
if (memcpy_fromiovec(skb_put(*frag, count), msg->msg_iov, count))
|
|
|
|
return -EFAULT;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-11-01 09:58:57 +01:00
|
|
|
(*frag)->priority = skb->priority;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
sent += count;
|
|
|
|
len -= count;
|
|
|
|
|
|
|
|
frag = &(*frag)->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sent;
|
2009-08-21 03:25:57 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-11-01 09:58:57 +01:00
|
|
|
static struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan,
|
|
|
|
struct msghdr *msg, size_t len,
|
|
|
|
u32 priority)
|
2009-08-21 03:25:57 +02:00
|
|
|
{
|
2011-04-14 00:50:45 +02:00
|
|
|
struct sock *sk = chan->sk;
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_conn *conn = chan->conn;
|
2009-08-21 03:25:57 +02:00
|
|
|
struct sk_buff *skb;
|
2011-10-17 11:19:58 +02:00
|
|
|
int err, count, hlen = L2CAP_HDR_SIZE + L2CAP_PSMLEN_SIZE;
|
2009-08-21 03:25:57 +02:00
|
|
|
struct l2cap_hdr *lh;
|
|
|
|
|
2011-11-01 09:58:57 +01:00
|
|
|
BT_DBG("sk %p len %d priority %u", sk, (int)len, priority);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
|
|
|
count = min_t(unsigned int, (conn->mtu - hlen), len);
|
|
|
|
skb = bt_skb_send_alloc(sk, count + hlen,
|
|
|
|
msg->msg_flags & MSG_DONTWAIT, &err);
|
|
|
|
if (!skb)
|
2010-09-25 01:30:57 +02:00
|
|
|
return ERR_PTR(err);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-11-01 09:58:57 +01:00
|
|
|
skb->priority = priority;
|
|
|
|
|
2009-08-21 03:25:57 +02:00
|
|
|
/* Create L2CAP header */
|
|
|
|
lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
|
2011-04-14 00:50:45 +02:00
|
|
|
lh->cid = cpu_to_le16(chan->dcid);
|
2009-08-21 03:25:57 +02:00
|
|
|
lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE));
|
2011-04-14 00:50:45 +02:00
|
|
|
put_unaligned_le16(chan->psm, skb_put(skb, 2));
|
2009-08-21 03:25:57 +02:00
|
|
|
|
|
|
|
err = l2cap_skbuff_fromiovec(sk, msg, len, count, skb);
|
|
|
|
if (unlikely(err < 0)) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return ERR_PTR(err);
|
|
|
|
}
|
|
|
|
return skb;
|
|
|
|
}
|
|
|
|
|
2011-11-01 09:58:57 +01:00
|
|
|
static struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan,
|
|
|
|
struct msghdr *msg, size_t len,
|
|
|
|
u32 priority)
|
2009-08-21 03:25:57 +02:00
|
|
|
{
|
2011-04-14 00:50:45 +02:00
|
|
|
struct sock *sk = chan->sk;
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_conn *conn = chan->conn;
|
2009-08-21 03:25:57 +02:00
|
|
|
struct sk_buff *skb;
|
|
|
|
int err, count, hlen = L2CAP_HDR_SIZE;
|
|
|
|
struct l2cap_hdr *lh;
|
|
|
|
|
|
|
|
BT_DBG("sk %p len %d", sk, (int)len);
|
|
|
|
|
|
|
|
count = min_t(unsigned int, (conn->mtu - hlen), len);
|
|
|
|
skb = bt_skb_send_alloc(sk, count + hlen,
|
|
|
|
msg->msg_flags & MSG_DONTWAIT, &err);
|
|
|
|
if (!skb)
|
2010-09-25 01:30:57 +02:00
|
|
|
return ERR_PTR(err);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-11-01 09:58:57 +01:00
|
|
|
skb->priority = priority;
|
|
|
|
|
2009-08-21 03:25:57 +02:00
|
|
|
/* Create L2CAP header */
|
|
|
|
lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
|
2011-04-14 00:50:45 +02:00
|
|
|
lh->cid = cpu_to_le16(chan->dcid);
|
2009-08-21 03:25:57 +02:00
|
|
|
lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE));
|
|
|
|
|
|
|
|
err = l2cap_skbuff_fromiovec(sk, msg, len, count, skb);
|
|
|
|
if (unlikely(err < 0)) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return ERR_PTR(err);
|
|
|
|
}
|
|
|
|
return skb;
|
|
|
|
}
|
|
|
|
|
2011-09-12 19:00:50 +02:00
|
|
|
static struct sk_buff *l2cap_create_iframe_pdu(struct l2cap_chan *chan,
|
|
|
|
struct msghdr *msg, size_t len,
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control, u16 sdulen)
|
2009-08-21 03:25:57 +02:00
|
|
|
{
|
2011-04-13 20:57:03 +02:00
|
|
|
struct sock *sk = chan->sk;
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_conn *conn = chan->conn;
|
2009-08-21 03:25:57 +02:00
|
|
|
struct sk_buff *skb;
|
2011-10-11 12:37:52 +02:00
|
|
|
int err, count, hlen;
|
2009-08-21 03:25:57 +02:00
|
|
|
struct l2cap_hdr *lh;
|
|
|
|
|
|
|
|
BT_DBG("sk %p len %d", sk, (int)len);
|
|
|
|
|
2010-05-01 21:15:41 +02:00
|
|
|
if (!conn)
|
|
|
|
return ERR_PTR(-ENOTCONN);
|
|
|
|
|
2011-10-11 12:37:52 +02:00
|
|
|
if (test_bit(FLAG_EXT_CTRL, &chan->flags))
|
|
|
|
hlen = L2CAP_EXT_HDR_SIZE;
|
|
|
|
else
|
|
|
|
hlen = L2CAP_ENH_HDR_SIZE;
|
|
|
|
|
2009-08-21 03:25:58 +02:00
|
|
|
if (sdulen)
|
2011-10-17 11:19:58 +02:00
|
|
|
hlen += L2CAP_SDULEN_SIZE;
|
2009-08-21 03:25:58 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16)
|
2011-10-17 11:19:58 +02:00
|
|
|
hlen += L2CAP_FCS_SIZE;
|
2009-08-21 03:26:02 +02:00
|
|
|
|
2009-08-21 03:25:57 +02:00
|
|
|
count = min_t(unsigned int, (conn->mtu - hlen), len);
|
|
|
|
skb = bt_skb_send_alloc(sk, count + hlen,
|
|
|
|
msg->msg_flags & MSG_DONTWAIT, &err);
|
|
|
|
if (!skb)
|
2010-09-25 01:30:57 +02:00
|
|
|
return ERR_PTR(err);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
|
|
|
/* Create L2CAP header */
|
|
|
|
lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
|
2011-04-14 00:50:45 +02:00
|
|
|
lh->cid = cpu_to_le16(chan->dcid);
|
2009-08-21 03:25:57 +02:00
|
|
|
lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE));
|
2011-10-17 11:19:56 +02:00
|
|
|
|
|
|
|
__put_control(chan, control, skb_put(skb, __ctrl_size(chan)));
|
|
|
|
|
2009-08-21 03:25:58 +02:00
|
|
|
if (sdulen)
|
2011-10-17 11:19:58 +02:00
|
|
|
put_unaligned_le16(sdulen, skb_put(skb, L2CAP_SDULEN_SIZE));
|
2009-08-21 03:25:57 +02:00
|
|
|
|
|
|
|
err = l2cap_skbuff_fromiovec(sk, msg, len, count, skb);
|
|
|
|
if (unlikely(err < 0)) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return ERR_PTR(err);
|
|
|
|
}
|
2009-08-21 03:26:00 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16)
|
2011-10-17 11:19:58 +02:00
|
|
|
put_unaligned_le16(0, skb_put(skb, L2CAP_FCS_SIZE));
|
2009-08-21 03:26:02 +02:00
|
|
|
|
2009-08-21 03:26:00 +02:00
|
|
|
bt_cb(skb)->retries = 0;
|
2009-08-21 03:25:57 +02:00
|
|
|
return skb;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2011-07-28 16:24:33 +02:00
|
|
|
static int l2cap_sar_segment_sdu(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
|
2009-08-21 03:25:58 +02:00
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct sk_buff_head sar_queue;
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2009-08-21 03:25:58 +02:00
|
|
|
size_t size = 0;
|
|
|
|
|
2010-05-06 03:09:15 +02:00
|
|
|
skb_queue_head_init(&sar_queue);
|
2011-10-11 12:37:45 +02:00
|
|
|
control = __set_ctrl_sar(chan, L2CAP_SAR_START);
|
2011-04-13 20:57:03 +02:00
|
|
|
skb = l2cap_create_iframe_pdu(chan, msg, chan->remote_mps, control, len);
|
2009-08-21 03:25:58 +02:00
|
|
|
if (IS_ERR(skb))
|
|
|
|
return PTR_ERR(skb);
|
|
|
|
|
|
|
|
__skb_queue_tail(&sar_queue, skb);
|
2011-03-26 00:15:28 +01:00
|
|
|
len -= chan->remote_mps;
|
|
|
|
size += chan->remote_mps;
|
2009-08-21 03:25:58 +02:00
|
|
|
|
|
|
|
while (len > 0) {
|
|
|
|
size_t buflen;
|
|
|
|
|
2011-03-26 00:15:28 +01:00
|
|
|
if (len > chan->remote_mps) {
|
2011-10-11 12:37:45 +02:00
|
|
|
control = __set_ctrl_sar(chan, L2CAP_SAR_CONTINUE);
|
2011-03-26 00:15:28 +01:00
|
|
|
buflen = chan->remote_mps;
|
2009-08-21 03:25:58 +02:00
|
|
|
} else {
|
2011-10-11 12:37:45 +02:00
|
|
|
control = __set_ctrl_sar(chan, L2CAP_SAR_END);
|
2009-08-21 03:25:58 +02:00
|
|
|
buflen = len;
|
|
|
|
}
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
skb = l2cap_create_iframe_pdu(chan, msg, buflen, control, 0);
|
2009-08-21 03:25:58 +02:00
|
|
|
if (IS_ERR(skb)) {
|
|
|
|
skb_queue_purge(&sar_queue);
|
|
|
|
return PTR_ERR(skb);
|
|
|
|
}
|
|
|
|
|
|
|
|
__skb_queue_tail(&sar_queue, skb);
|
|
|
|
len -= buflen;
|
|
|
|
size += buflen;
|
|
|
|
}
|
2011-04-04 21:16:44 +02:00
|
|
|
skb_queue_splice_tail(&sar_queue, &chan->tx_q);
|
|
|
|
if (chan->tx_send_head == NULL)
|
|
|
|
chan->tx_send_head = sar_queue.next;
|
2009-08-21 03:25:58 +02:00
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2011-11-01 09:58:57 +01:00
|
|
|
int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
|
|
|
|
u32 priority)
|
2011-04-28 23:50:17 +02:00
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2011-04-28 23:50:17 +02:00
|
|
|
int err;
|
|
|
|
|
|
|
|
/* Connectionless channel */
|
2011-05-02 22:13:55 +02:00
|
|
|
if (chan->chan_type == L2CAP_CHAN_CONN_LESS) {
|
2011-11-01 09:58:57 +01:00
|
|
|
skb = l2cap_create_connless_pdu(chan, msg, len, priority);
|
2011-04-28 23:50:17 +02:00
|
|
|
if (IS_ERR(skb))
|
|
|
|
return PTR_ERR(skb);
|
|
|
|
|
|
|
|
l2cap_do_send(chan, skb);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (chan->mode) {
|
|
|
|
case L2CAP_MODE_BASIC:
|
|
|
|
/* Check outgoing MTU */
|
|
|
|
if (len > chan->omtu)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
/* Create a basic PDU */
|
2011-11-01 09:58:57 +01:00
|
|
|
skb = l2cap_create_basic_pdu(chan, msg, len, priority);
|
2011-04-28 23:50:17 +02:00
|
|
|
if (IS_ERR(skb))
|
|
|
|
return PTR_ERR(skb);
|
|
|
|
|
|
|
|
l2cap_do_send(chan, skb);
|
|
|
|
err = len;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MODE_ERTM:
|
|
|
|
case L2CAP_MODE_STREAMING:
|
|
|
|
/* Entire SDU fits into one PDU */
|
|
|
|
if (len <= chan->remote_mps) {
|
2011-10-11 12:37:45 +02:00
|
|
|
control = __set_ctrl_sar(chan, L2CAP_SAR_UNSEGMENTED);
|
2011-04-28 23:50:17 +02:00
|
|
|
skb = l2cap_create_iframe_pdu(chan, msg, len, control,
|
|
|
|
0);
|
|
|
|
if (IS_ERR(skb))
|
|
|
|
return PTR_ERR(skb);
|
|
|
|
|
|
|
|
__skb_queue_tail(&chan->tx_q, skb);
|
|
|
|
|
|
|
|
if (chan->tx_send_head == NULL)
|
|
|
|
chan->tx_send_head = skb;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* Segment SDU into multiples PDUs */
|
|
|
|
err = l2cap_sar_segment_sdu(chan, msg, len);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chan->mode == L2CAP_MODE_STREAMING) {
|
|
|
|
l2cap_streaming_send(chan);
|
|
|
|
err = len;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state) &&
|
|
|
|
test_bit(CONN_WAIT_F, &chan->conn_state)) {
|
2011-04-28 23:50:17 +02:00
|
|
|
err = len;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = l2cap_ertm_send(chan);
|
|
|
|
if (err >= 0)
|
|
|
|
err = len;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
BT_DBG("bad state %1.1x", chan->mode);
|
|
|
|
err = -EBADFD;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Copy frame to all raw sockets on that connection */
|
|
|
|
static void l2cap_raw_recv(struct l2cap_conn *conn, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct sk_buff *nskb;
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
BT_DBG("conn %p", conn);
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
read_lock(&conn->chan_lock);
|
|
|
|
list_for_each_entry(chan, &conn->chan_l, list) {
|
2011-03-25 04:22:30 +01:00
|
|
|
struct sock *sk = chan->sk;
|
2011-05-02 22:13:55 +02:00
|
|
|
if (chan->chan_type != L2CAP_CHAN_RAW)
|
2005-04-17 00:20:36 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Don't send frame to the socket it came from */
|
|
|
|
if (skb->sk == sk)
|
|
|
|
continue;
|
2009-04-20 06:31:08 +02:00
|
|
|
nskb = skb_clone(skb, GFP_ATOMIC);
|
|
|
|
if (!nskb)
|
2005-04-17 00:20:36 +02:00
|
|
|
continue;
|
|
|
|
|
2011-05-16 22:57:22 +02:00
|
|
|
if (chan->ops->recv(chan->data, nskb))
|
2005-04-17 00:20:36 +02:00
|
|
|
kfree_skb(nskb);
|
|
|
|
}
|
2011-03-31 21:17:41 +02:00
|
|
|
read_unlock(&conn->chan_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ---- L2CAP signalling commands ---- */
|
|
|
|
static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
|
|
|
|
u8 code, u8 ident, u16 dlen, void *data)
|
|
|
|
{
|
|
|
|
struct sk_buff *skb, **frag;
|
|
|
|
struct l2cap_cmd_hdr *cmd;
|
|
|
|
struct l2cap_hdr *lh;
|
|
|
|
int len, count;
|
|
|
|
|
2009-04-20 06:31:08 +02:00
|
|
|
BT_DBG("conn %p, code 0x%2.2x, ident 0x%2.2x, len %d",
|
|
|
|
conn, code, ident, dlen);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
len = L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE + dlen;
|
|
|
|
count = min_t(unsigned int, conn->mtu, len);
|
|
|
|
|
|
|
|
skb = bt_skb_alloc(count, GFP_ATOMIC);
|
|
|
|
if (!skb)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
|
2007-03-26 05:12:50 +02:00
|
|
|
lh->len = cpu_to_le16(L2CAP_CMD_HDR_SIZE + dlen);
|
2011-02-11 22:28:54 +01:00
|
|
|
|
|
|
|
if (conn->hcon->type == LE_LINK)
|
|
|
|
lh->cid = cpu_to_le16(L2CAP_CID_LE_SIGNALING);
|
|
|
|
else
|
|
|
|
lh->cid = cpu_to_le16(L2CAP_CID_SIGNALING);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
cmd = (struct l2cap_cmd_hdr *) skb_put(skb, L2CAP_CMD_HDR_SIZE);
|
|
|
|
cmd->code = code;
|
|
|
|
cmd->ident = ident;
|
2007-03-26 05:12:50 +02:00
|
|
|
cmd->len = cpu_to_le16(dlen);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (dlen) {
|
|
|
|
count -= L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE;
|
|
|
|
memcpy(skb_put(skb, count), data, count);
|
|
|
|
data += count;
|
|
|
|
}
|
|
|
|
|
|
|
|
len -= skb->len;
|
|
|
|
|
|
|
|
/* Continuation fragments (no L2CAP header) */
|
|
|
|
frag = &skb_shinfo(skb)->frag_list;
|
|
|
|
while (len) {
|
|
|
|
count = min_t(unsigned int, conn->mtu, len);
|
|
|
|
|
|
|
|
*frag = bt_skb_alloc(count, GFP_ATOMIC);
|
|
|
|
if (!*frag)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
memcpy(skb_put(*frag, count), data, count);
|
|
|
|
|
|
|
|
len -= count;
|
|
|
|
data += count;
|
|
|
|
|
|
|
|
frag = &(*frag)->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return skb;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, unsigned long *val)
|
|
|
|
{
|
|
|
|
struct l2cap_conf_opt *opt = *ptr;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
len = L2CAP_CONF_OPT_SIZE + opt->len;
|
|
|
|
*ptr += len;
|
|
|
|
|
|
|
|
*type = opt->type;
|
|
|
|
*olen = opt->len;
|
|
|
|
|
|
|
|
switch (opt->len) {
|
|
|
|
case 1:
|
|
|
|
*val = *((u8 *) opt->val);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
2010-10-17 00:29:47 +02:00
|
|
|
*val = get_unaligned_le16(opt->val);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 4:
|
2010-10-17 00:29:47 +02:00
|
|
|
*val = get_unaligned_le32(opt->val);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
*val = (unsigned long) opt->val;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
BT_DBG("type 0x%2.2x len %d val 0x%lx", *type, opt->len, *val);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_add_conf_opt(void **ptr, u8 type, u8 len, unsigned long val)
|
|
|
|
{
|
|
|
|
struct l2cap_conf_opt *opt = *ptr;
|
|
|
|
|
|
|
|
BT_DBG("type 0x%2.2x len %d val 0x%lx", type, len, val);
|
|
|
|
|
|
|
|
opt->type = type;
|
|
|
|
opt->len = len;
|
|
|
|
|
|
|
|
switch (len) {
|
|
|
|
case 1:
|
|
|
|
*((u8 *) opt->val) = val;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
2010-10-18 18:25:53 +02:00
|
|
|
put_unaligned_le16(val, opt->val);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 4:
|
2010-10-18 18:25:53 +02:00
|
|
|
put_unaligned_le32(val, opt->val);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
memcpy(opt->val, (void *) val, len);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ptr += L2CAP_CONF_OPT_SIZE + len;
|
|
|
|
}
|
|
|
|
|
Bluetooth: EFS: add efs option in L2CAP conf req
Add Extended Flow Specification option when building L2CAP
Configuration Request. EFS is added if both the local and
remote L2CAP entities have indicated support for the
Extended Flow Specification for BR/EDR.
...
< ACL data: handle 1 flags 0x00 dlen 10
L2CAP(s): Info req: type 2
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Info rsp: type 2 result 0
Extended feature mask 0x01f8
Enhanced Retransmission mode
Streaming mode
FCS Option
Extended Flow Specification
Fixed Channels
Extended Window Size
...
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
...
Based upon haijun.liu <haijun.liu@atheros.com> series of patches
(sent Sun, 22 Aug 2010)
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-13 15:18:55 +02:00
|
|
|
static void l2cap_add_opt_efs(void **ptr, struct l2cap_chan *chan)
|
|
|
|
{
|
|
|
|
struct l2cap_conf_efs efs;
|
|
|
|
|
|
|
|
switch(chan->mode) {
|
|
|
|
case L2CAP_MODE_ERTM:
|
|
|
|
efs.id = chan->local_id;
|
|
|
|
efs.stype = chan->local_stype;
|
|
|
|
efs.msdu = cpu_to_le16(chan->local_msdu);
|
|
|
|
efs.sdu_itime = cpu_to_le32(chan->local_sdu_itime);
|
|
|
|
efs.acc_lat = cpu_to_le32(L2CAP_DEFAULT_ACC_LAT);
|
|
|
|
efs.flush_to = cpu_to_le32(L2CAP_DEFAULT_FLUSH_TO);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MODE_STREAMING:
|
|
|
|
efs.id = 1;
|
|
|
|
efs.stype = L2CAP_SERV_BESTEFFORT;
|
|
|
|
efs.msdu = cpu_to_le16(chan->local_msdu);
|
|
|
|
efs.sdu_itime = cpu_to_le32(chan->local_sdu_itime);
|
|
|
|
efs.acc_lat = 0;
|
|
|
|
efs.flush_to = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
l2cap_add_conf_opt(ptr, L2CAP_CONF_EFS, sizeof(efs),
|
|
|
|
(unsigned long) &efs);
|
|
|
|
}
|
|
|
|
|
2010-05-01 21:15:39 +02:00
|
|
|
static void l2cap_ack_timeout(unsigned long arg)
|
|
|
|
{
|
2011-03-25 23:43:39 +01:00
|
|
|
struct l2cap_chan *chan = (void *) arg;
|
2010-05-01 21:15:39 +02:00
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
bh_lock_sock(chan->sk);
|
|
|
|
l2cap_send_ack(chan);
|
|
|
|
bh_unlock_sock(chan->sk);
|
2010-05-01 21:15:39 +02:00
|
|
|
}
|
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
static inline void l2cap_ertm_init(struct l2cap_chan *chan)
|
2009-10-03 07:34:36 +02:00
|
|
|
{
|
2011-03-25 23:43:39 +01:00
|
|
|
struct sock *sk = chan->sk;
|
|
|
|
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->expected_ack_seq = 0;
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->unacked_frames = 0;
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->buffer_seq = 0;
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->num_acked = 0;
|
|
|
|
chan->frames_sent = 0;
|
2009-10-03 07:34:36 +02:00
|
|
|
|
2011-04-01 05:53:45 +02:00
|
|
|
setup_timer(&chan->retrans_timer, l2cap_retrans_timeout,
|
|
|
|
(unsigned long) chan);
|
|
|
|
setup_timer(&chan->monitor_timer, l2cap_monitor_timeout,
|
|
|
|
(unsigned long) chan);
|
|
|
|
setup_timer(&chan->ack_timer, l2cap_ack_timeout, (unsigned long) chan);
|
2009-10-03 07:34:36 +02:00
|
|
|
|
2011-03-26 00:36:10 +01:00
|
|
|
skb_queue_head_init(&chan->srej_q);
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-04-04 20:40:12 +02:00
|
|
|
INIT_LIST_HEAD(&chan->srej_l);
|
|
|
|
|
2010-06-21 23:53:22 +02:00
|
|
|
|
|
|
|
sk->sk_backlog_rcv = l2cap_ertm_data_rcv;
|
2009-10-03 07:34:36 +02:00
|
|
|
}
|
|
|
|
|
2009-07-04 20:06:24 +02:00
|
|
|
static inline __u8 l2cap_select_mode(__u8 mode, __u16 remote_feat_mask)
|
|
|
|
{
|
|
|
|
switch (mode) {
|
|
|
|
case L2CAP_MODE_STREAMING:
|
|
|
|
case L2CAP_MODE_ERTM:
|
|
|
|
if (l2cap_mode_supported(mode, remote_feat_mask))
|
|
|
|
return mode;
|
|
|
|
/* fall through */
|
|
|
|
default:
|
|
|
|
return L2CAP_MODE_BASIC;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-11 12:37:42 +02:00
|
|
|
static inline bool __l2cap_ews_supported(struct l2cap_chan *chan)
|
|
|
|
{
|
|
|
|
return enable_hs && chan->conn->feat_mask & L2CAP_FEAT_EXT_WINDOW;
|
|
|
|
}
|
|
|
|
|
Bluetooth: EFS: add efs option in L2CAP conf req
Add Extended Flow Specification option when building L2CAP
Configuration Request. EFS is added if both the local and
remote L2CAP entities have indicated support for the
Extended Flow Specification for BR/EDR.
...
< ACL data: handle 1 flags 0x00 dlen 10
L2CAP(s): Info req: type 2
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Info rsp: type 2 result 0
Extended feature mask 0x01f8
Enhanced Retransmission mode
Streaming mode
FCS Option
Extended Flow Specification
Fixed Channels
Extended Window Size
...
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
...
Based upon haijun.liu <haijun.liu@atheros.com> series of patches
(sent Sun, 22 Aug 2010)
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-13 15:18:55 +02:00
|
|
|
static inline bool __l2cap_efs_supported(struct l2cap_chan *chan)
|
|
|
|
{
|
|
|
|
return enable_hs && chan->conn->feat_mask & L2CAP_FEAT_EXT_FLOW;
|
|
|
|
}
|
|
|
|
|
2011-10-11 12:37:42 +02:00
|
|
|
static inline void l2cap_txwin_setup(struct l2cap_chan *chan)
|
|
|
|
{
|
|
|
|
if (chan->tx_win > L2CAP_DEFAULT_TX_WINDOW &&
|
2011-10-17 11:19:57 +02:00
|
|
|
__l2cap_ews_supported(chan)) {
|
2011-10-11 12:37:42 +02:00
|
|
|
/* use extended control field */
|
|
|
|
set_bit(FLAG_EXT_CTRL, &chan->flags);
|
2011-10-17 11:19:57 +02:00
|
|
|
chan->tx_win_max = L2CAP_DEFAULT_EXT_WINDOW;
|
|
|
|
} else {
|
2011-10-11 12:37:42 +02:00
|
|
|
chan->tx_win = min_t(u16, chan->tx_win,
|
|
|
|
L2CAP_DEFAULT_TX_WINDOW);
|
2011-10-17 11:19:57 +02:00
|
|
|
chan->tx_win_max = L2CAP_DEFAULT_TX_WINDOW;
|
|
|
|
}
|
2011-10-11 12:37:42 +02:00
|
|
|
}
|
|
|
|
|
2011-03-25 18:30:37 +01:00
|
|
|
static int l2cap_build_conf_req(struct l2cap_chan *chan, void *data)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct l2cap_conf_req *req = data;
|
2011-04-13 22:20:49 +02:00
|
|
|
struct l2cap_conf_rfc rfc = { .mode = chan->mode };
|
2005-04-17 00:20:36 +02:00
|
|
|
void *ptr = req->data;
|
2011-10-17 11:19:59 +02:00
|
|
|
u16 size;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-04 20:59:54 +02:00
|
|
|
BT_DBG("chan %p", chan);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-03-25 18:16:54 +01:00
|
|
|
if (chan->num_conf_req || chan->num_conf_rsp)
|
2009-07-04 20:06:24 +02:00
|
|
|
goto done;
|
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
switch (chan->mode) {
|
2009-07-04 20:06:24 +02:00
|
|
|
case L2CAP_MODE_STREAMING:
|
|
|
|
case L2CAP_MODE_ERTM:
|
2011-06-10 22:02:12 +02:00
|
|
|
if (test_bit(CONF_STATE2_DEVICE, &chan->conf_state))
|
2010-06-03 23:43:28 +02:00
|
|
|
break;
|
|
|
|
|
Bluetooth: EFS: add efs option in L2CAP conf req
Add Extended Flow Specification option when building L2CAP
Configuration Request. EFS is added if both the local and
remote L2CAP entities have indicated support for the
Extended Flow Specification for BR/EDR.
...
< ACL data: handle 1 flags 0x00 dlen 10
L2CAP(s): Info req: type 2
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Info rsp: type 2 result 0
Extended feature mask 0x01f8
Enhanced Retransmission mode
Streaming mode
FCS Option
Extended Flow Specification
Fixed Channels
Extended Window Size
...
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
...
Based upon haijun.liu <haijun.liu@atheros.com> series of patches
(sent Sun, 22 Aug 2010)
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-13 15:18:55 +02:00
|
|
|
if (__l2cap_efs_supported(chan))
|
|
|
|
set_bit(FLAG_EFS_ENABLE, &chan->flags);
|
|
|
|
|
2010-06-09 21:39:05 +02:00
|
|
|
/* fall through */
|
2009-07-04 20:06:24 +02:00
|
|
|
default:
|
2011-04-14 01:23:55 +02:00
|
|
|
chan->mode = l2cap_select_mode(rfc.mode, chan->conn->feat_mask);
|
2009-07-04 20:06:24 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->imtu != L2CAP_DEFAULT_MTU)
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, chan->imtu);
|
2011-01-24 19:01:43 +01:00
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
switch (chan->mode) {
|
2009-05-03 08:07:53 +02:00
|
|
|
case L2CAP_MODE_BASIC:
|
2011-04-14 01:23:55 +02:00
|
|
|
if (!(chan->conn->feat_mask & L2CAP_FEAT_ERTM) &&
|
|
|
|
!(chan->conn->feat_mask & L2CAP_FEAT_STREAMING))
|
2010-08-04 04:49:29 +02:00
|
|
|
break;
|
|
|
|
|
2010-06-09 01:05:31 +02:00
|
|
|
rfc.mode = L2CAP_MODE_BASIC;
|
|
|
|
rfc.txwin_size = 0;
|
|
|
|
rfc.max_transmit = 0;
|
|
|
|
rfc.retrans_timeout = 0;
|
|
|
|
rfc.monitor_timeout = 0;
|
|
|
|
rfc.max_pdu_size = 0;
|
|
|
|
|
2010-08-04 04:49:29 +02:00
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc),
|
|
|
|
(unsigned long) &rfc);
|
2009-05-03 08:07:53 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MODE_ERTM:
|
|
|
|
rfc.mode = L2CAP_MODE_ERTM;
|
2011-04-13 20:57:03 +02:00
|
|
|
rfc.max_transmit = chan->max_tx;
|
2009-07-04 20:06:24 +02:00
|
|
|
rfc.retrans_timeout = 0;
|
|
|
|
rfc.monitor_timeout = 0;
|
2011-10-17 11:19:59 +02:00
|
|
|
|
|
|
|
size = min_t(u16, L2CAP_DEFAULT_MAX_PDU_SIZE, chan->conn->mtu -
|
|
|
|
L2CAP_EXT_HDR_SIZE -
|
|
|
|
L2CAP_SDULEN_SIZE -
|
|
|
|
L2CAP_FCS_SIZE);
|
|
|
|
rfc.max_pdu_size = cpu_to_le16(size);
|
2009-07-04 20:06:24 +02:00
|
|
|
|
2011-10-11 12:37:42 +02:00
|
|
|
l2cap_txwin_setup(chan);
|
|
|
|
|
|
|
|
rfc.txwin_size = min_t(u16, chan->tx_win,
|
|
|
|
L2CAP_DEFAULT_TX_WINDOW);
|
2009-07-04 20:06:24 +02:00
|
|
|
|
2010-08-04 04:49:29 +02:00
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc),
|
|
|
|
(unsigned long) &rfc);
|
|
|
|
|
Bluetooth: EFS: add efs option in L2CAP conf req
Add Extended Flow Specification option when building L2CAP
Configuration Request. EFS is added if both the local and
remote L2CAP entities have indicated support for the
Extended Flow Specification for BR/EDR.
...
< ACL data: handle 1 flags 0x00 dlen 10
L2CAP(s): Info req: type 2
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Info rsp: type 2 result 0
Extended feature mask 0x01f8
Enhanced Retransmission mode
Streaming mode
FCS Option
Extended Flow Specification
Fixed Channels
Extended Window Size
...
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
...
Based upon haijun.liu <haijun.liu@atheros.com> series of patches
(sent Sun, 22 Aug 2010)
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-13 15:18:55 +02:00
|
|
|
if (test_bit(FLAG_EFS_ENABLE, &chan->flags))
|
|
|
|
l2cap_add_opt_efs(&ptr, chan);
|
|
|
|
|
2011-04-14 01:23:55 +02:00
|
|
|
if (!(chan->conn->feat_mask & L2CAP_FEAT_FCS))
|
2009-08-21 03:26:02 +02:00
|
|
|
break;
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_NONE ||
|
2011-06-10 22:02:12 +02:00
|
|
|
test_bit(CONF_NO_FCS_RECV, &chan->conf_state)) {
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->fcs = L2CAP_FCS_NONE;
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, chan->fcs);
|
2009-08-21 03:26:02 +02:00
|
|
|
}
|
2011-10-11 12:37:42 +02:00
|
|
|
|
|
|
|
if (test_bit(FLAG_EXT_CTRL, &chan->flags))
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EWS, 2,
|
|
|
|
chan->tx_win);
|
2009-07-04 20:06:24 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MODE_STREAMING:
|
|
|
|
rfc.mode = L2CAP_MODE_STREAMING;
|
|
|
|
rfc.txwin_size = 0;
|
|
|
|
rfc.max_transmit = 0;
|
|
|
|
rfc.retrans_timeout = 0;
|
|
|
|
rfc.monitor_timeout = 0;
|
2011-10-17 11:19:59 +02:00
|
|
|
|
|
|
|
size = min_t(u16, L2CAP_DEFAULT_MAX_PDU_SIZE, chan->conn->mtu -
|
|
|
|
L2CAP_EXT_HDR_SIZE -
|
|
|
|
L2CAP_SDULEN_SIZE -
|
|
|
|
L2CAP_FCS_SIZE);
|
|
|
|
rfc.max_pdu_size = cpu_to_le16(size);
|
2009-05-03 08:07:53 +02:00
|
|
|
|
2010-08-04 04:49:29 +02:00
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc),
|
|
|
|
(unsigned long) &rfc);
|
|
|
|
|
Bluetooth: EFS: add efs option in L2CAP conf req
Add Extended Flow Specification option when building L2CAP
Configuration Request. EFS is added if both the local and
remote L2CAP entities have indicated support for the
Extended Flow Specification for BR/EDR.
...
< ACL data: handle 1 flags 0x00 dlen 10
L2CAP(s): Info req: type 2
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Info rsp: type 2 result 0
Extended feature mask 0x01f8
Enhanced Retransmission mode
Streaming mode
FCS Option
Extended Flow Specification
Fixed Channels
Extended Window Size
...
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
...
Based upon haijun.liu <haijun.liu@atheros.com> series of patches
(sent Sun, 22 Aug 2010)
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-13 15:18:55 +02:00
|
|
|
if (test_bit(FLAG_EFS_ENABLE, &chan->flags))
|
|
|
|
l2cap_add_opt_efs(&ptr, chan);
|
|
|
|
|
2011-04-14 01:23:55 +02:00
|
|
|
if (!(chan->conn->feat_mask & L2CAP_FEAT_FCS))
|
2009-08-21 03:26:02 +02:00
|
|
|
break;
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_NONE ||
|
2011-06-10 22:02:12 +02:00
|
|
|
test_bit(CONF_NO_FCS_RECV, &chan->conf_state)) {
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->fcs = L2CAP_FCS_NONE;
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, chan->fcs);
|
2009-08-21 03:26:02 +02:00
|
|
|
}
|
2009-05-03 08:07:53 +02:00
|
|
|
break;
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
req->dcid = cpu_to_le16(chan->dcid);
|
2007-03-26 05:12:50 +02:00
|
|
|
req->flags = cpu_to_le16(0);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return ptr - data;
|
|
|
|
}
|
|
|
|
|
2011-03-25 18:16:54 +01:00
|
|
|
static int l2cap_parse_conf_req(struct l2cap_chan *chan, void *data)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2007-05-24 14:27:19 +02:00
|
|
|
struct l2cap_conf_rsp *rsp = data;
|
|
|
|
void *ptr = rsp->data;
|
2011-03-25 18:16:54 +01:00
|
|
|
void *req = chan->conf_req;
|
|
|
|
int len = chan->conf_len;
|
2007-05-24 14:27:19 +02:00
|
|
|
int type, hint, olen;
|
|
|
|
unsigned long val;
|
2007-10-20 13:39:51 +02:00
|
|
|
struct l2cap_conf_rfc rfc = { .mode = L2CAP_MODE_BASIC };
|
2011-10-17 13:35:30 +02:00
|
|
|
struct l2cap_conf_efs efs;
|
|
|
|
u8 remote_efs = 0;
|
2007-10-20 13:37:06 +02:00
|
|
|
u16 mtu = L2CAP_DEFAULT_MTU;
|
2007-05-24 14:27:19 +02:00
|
|
|
u16 result = L2CAP_CONF_SUCCESS;
|
2011-10-17 11:19:59 +02:00
|
|
|
u16 size;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-03-25 18:16:54 +01:00
|
|
|
BT_DBG("chan %p", chan);
|
2006-11-18 22:15:00 +01:00
|
|
|
|
2007-05-24 14:27:19 +02:00
|
|
|
while (len >= L2CAP_CONF_OPT_SIZE) {
|
|
|
|
len -= l2cap_get_conf_opt(&req, &type, &olen, &val);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2009-04-20 06:31:07 +02:00
|
|
|
hint = type & L2CAP_CONF_HINT;
|
2009-05-03 03:57:55 +02:00
|
|
|
type &= L2CAP_CONF_MASK;
|
2007-05-24 14:27:19 +02:00
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case L2CAP_CONF_MTU:
|
2007-10-20 13:37:06 +02:00
|
|
|
mtu = val;
|
2007-05-24 14:27:19 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CONF_FLUSH_TO:
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->flush_to = val;
|
2007-05-24 14:27:19 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CONF_QOS:
|
|
|
|
break;
|
|
|
|
|
2007-10-20 13:39:51 +02:00
|
|
|
case L2CAP_CONF_RFC:
|
|
|
|
if (olen == sizeof(rfc))
|
|
|
|
memcpy(&rfc, (void *) val, olen);
|
|
|
|
break;
|
|
|
|
|
2009-08-21 03:26:02 +02:00
|
|
|
case L2CAP_CONF_FCS:
|
|
|
|
if (val == L2CAP_FCS_NONE)
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_NO_FCS_RECV, &chan->conf_state);
|
2011-10-17 13:35:30 +02:00
|
|
|
break;
|
2009-08-21 03:26:02 +02:00
|
|
|
|
2011-10-17 13:35:30 +02:00
|
|
|
case L2CAP_CONF_EFS:
|
|
|
|
remote_efs = 1;
|
|
|
|
if (olen == sizeof(efs))
|
|
|
|
memcpy(&efs, (void *) val, olen);
|
2009-08-21 03:26:02 +02:00
|
|
|
break;
|
|
|
|
|
2011-10-11 12:37:42 +02:00
|
|
|
case L2CAP_CONF_EWS:
|
|
|
|
if (!enable_hs)
|
|
|
|
return -ECONNREFUSED;
|
2009-08-21 03:26:02 +02:00
|
|
|
|
2011-10-11 12:37:42 +02:00
|
|
|
set_bit(FLAG_EXT_CTRL, &chan->flags);
|
|
|
|
set_bit(CONF_EWS_RECV, &chan->conf_state);
|
2011-10-17 11:19:57 +02:00
|
|
|
chan->tx_win_max = L2CAP_DEFAULT_EXT_WINDOW;
|
2011-10-11 12:37:42 +02:00
|
|
|
chan->remote_tx_win = val;
|
2009-08-21 03:26:02 +02:00
|
|
|
break;
|
|
|
|
|
2007-05-24 14:27:19 +02:00
|
|
|
default:
|
|
|
|
if (hint)
|
|
|
|
break;
|
|
|
|
|
|
|
|
result = L2CAP_CONF_UNKNOWN;
|
|
|
|
*((u8 *) ptr++) = type;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-25 18:16:54 +01:00
|
|
|
if (chan->num_conf_rsp || chan->num_conf_req > 1)
|
2009-07-04 20:06:24 +02:00
|
|
|
goto done;
|
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
switch (chan->mode) {
|
2009-07-04 20:06:24 +02:00
|
|
|
case L2CAP_MODE_STREAMING:
|
|
|
|
case L2CAP_MODE_ERTM:
|
2011-06-10 22:02:12 +02:00
|
|
|
if (!test_bit(CONF_STATE2_DEVICE, &chan->conf_state)) {
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->mode = l2cap_select_mode(rfc.mode,
|
2011-04-14 01:23:55 +02:00
|
|
|
chan->conn->feat_mask);
|
2010-06-03 23:43:28 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-10-17 13:35:30 +02:00
|
|
|
if (remote_efs) {
|
|
|
|
if (__l2cap_efs_supported(chan))
|
|
|
|
set_bit(FLAG_EFS_ENABLE, &chan->flags);
|
|
|
|
else
|
|
|
|
return -ECONNREFUSED;
|
|
|
|
}
|
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->mode != rfc.mode)
|
2009-07-04 20:06:24 +02:00
|
|
|
return -ECONNREFUSED;
|
2010-06-09 00:09:48 +02:00
|
|
|
|
2009-07-04 20:06:24 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->mode != rfc.mode) {
|
2009-07-04 20:06:24 +02:00
|
|
|
result = L2CAP_CONF_UNACCEPT;
|
2011-04-13 22:20:49 +02:00
|
|
|
rfc.mode = chan->mode;
|
2009-07-04 20:06:24 +02:00
|
|
|
|
2011-03-25 18:16:54 +01:00
|
|
|
if (chan->num_conf_rsp == 1)
|
2009-07-04 20:06:24 +02:00
|
|
|
return -ECONNREFUSED;
|
|
|
|
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
|
|
|
|
sizeof(rfc), (unsigned long) &rfc);
|
|
|
|
}
|
|
|
|
|
2007-05-24 14:27:19 +02:00
|
|
|
if (result == L2CAP_CONF_SUCCESS) {
|
|
|
|
/* Configure output options and let the other side know
|
|
|
|
* which ones we don't like. */
|
|
|
|
|
2009-07-04 20:06:24 +02:00
|
|
|
if (mtu < L2CAP_DEFAULT_MIN_MTU)
|
|
|
|
result = L2CAP_CONF_UNACCEPT;
|
|
|
|
else {
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->omtu = mtu;
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_MTU_DONE, &chan->conf_state);
|
2009-07-04 20:06:24 +02:00
|
|
|
}
|
2011-04-13 22:20:49 +02:00
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, chan->omtu);
|
2007-10-20 13:39:51 +02:00
|
|
|
|
2011-10-17 13:35:30 +02:00
|
|
|
if (remote_efs) {
|
|
|
|
if (chan->local_stype != L2CAP_SERV_NOTRAFIC &&
|
|
|
|
efs.stype != L2CAP_SERV_NOTRAFIC &&
|
|
|
|
efs.stype != chan->local_stype) {
|
|
|
|
|
|
|
|
result = L2CAP_CONF_UNACCEPT;
|
|
|
|
|
|
|
|
if (chan->num_conf_req >= 1)
|
|
|
|
return -ECONNREFUSED;
|
|
|
|
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EFS,
|
2011-11-01 17:06:23 +01:00
|
|
|
sizeof(efs),
|
2011-10-17 13:35:30 +02:00
|
|
|
(unsigned long) &efs);
|
Bluetooth: EFS: implement L2CAP config pending state
Add L2CAP Config Pending state for EFS. Currently after receiving
Config Response Pending respond with Config Response Success.
...
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0040 scid 0x0040 result 0 status 0
Connection successful
> ACL data: handle 1 flags 0x02 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 1009)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 510
L2CAP(d): cid 0x0040 len 506 ext_ctrl 0x00010000 fcs 0xebe0 [psm 4113]
I-frame: Start (len 672) TxSeq 0 ReqSeq 0
...
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-17 13:35:32 +02:00
|
|
|
} else {
|
2011-11-01 17:06:23 +01:00
|
|
|
/* Send PENDING Conf Rsp */
|
Bluetooth: EFS: implement L2CAP config pending state
Add L2CAP Config Pending state for EFS. Currently after receiving
Config Response Pending respond with Config Response Success.
...
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0040 scid 0x0040 result 0 status 0
Connection successful
> ACL data: handle 1 flags 0x02 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 1009)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 510
L2CAP(d): cid 0x0040 len 506 ext_ctrl 0x00010000 fcs 0xebe0 [psm 4113]
I-frame: Start (len 672) TxSeq 0 ReqSeq 0
...
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-17 13:35:32 +02:00
|
|
|
result = L2CAP_CONF_PENDING;
|
|
|
|
set_bit(CONF_LOC_CONF_PEND, &chan->conf_state);
|
2011-10-17 13:35:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-04 20:06:24 +02:00
|
|
|
switch (rfc.mode) {
|
|
|
|
case L2CAP_MODE_BASIC:
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->fcs = L2CAP_FCS_NONE;
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_MODE_DONE, &chan->conf_state);
|
2009-07-04 20:06:24 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MODE_ERTM:
|
2011-10-11 12:37:42 +02:00
|
|
|
if (!test_bit(CONF_EWS_RECV, &chan->conf_state))
|
|
|
|
chan->remote_tx_win = rfc.txwin_size;
|
|
|
|
else
|
|
|
|
rfc.txwin_size = L2CAP_DEFAULT_TX_WINDOW;
|
2010-08-06 00:54:22 +02:00
|
|
|
|
2011-03-26 00:15:28 +01:00
|
|
|
chan->remote_max_tx = rfc.max_transmit;
|
2010-05-01 21:15:40 +02:00
|
|
|
|
2011-10-17 11:19:59 +02:00
|
|
|
size = min_t(u16, le16_to_cpu(rfc.max_pdu_size),
|
|
|
|
chan->conn->mtu -
|
|
|
|
L2CAP_EXT_HDR_SIZE -
|
|
|
|
L2CAP_SDULEN_SIZE -
|
|
|
|
L2CAP_FCS_SIZE);
|
|
|
|
rfc.max_pdu_size = cpu_to_le16(size);
|
|
|
|
chan->remote_mps = size;
|
2009-07-04 20:06:24 +02:00
|
|
|
|
2010-05-01 21:15:40 +02:00
|
|
|
rfc.retrans_timeout =
|
|
|
|
le16_to_cpu(L2CAP_DEFAULT_RETRANS_TO);
|
|
|
|
rfc.monitor_timeout =
|
|
|
|
le16_to_cpu(L2CAP_DEFAULT_MONITOR_TO);
|
2009-07-04 20:06:24 +02:00
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_MODE_DONE, &chan->conf_state);
|
2009-10-18 02:41:01 +02:00
|
|
|
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
|
|
|
|
sizeof(rfc), (unsigned long) &rfc);
|
|
|
|
|
2011-10-17 13:35:30 +02:00
|
|
|
if (test_bit(FLAG_EFS_ENABLE, &chan->flags)) {
|
|
|
|
chan->remote_id = efs.id;
|
|
|
|
chan->remote_stype = efs.stype;
|
|
|
|
chan->remote_msdu = le16_to_cpu(efs.msdu);
|
|
|
|
chan->remote_flush_to =
|
|
|
|
le32_to_cpu(efs.flush_to);
|
|
|
|
chan->remote_acc_lat =
|
|
|
|
le32_to_cpu(efs.acc_lat);
|
|
|
|
chan->remote_sdu_itime =
|
|
|
|
le32_to_cpu(efs.sdu_itime);
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EFS,
|
|
|
|
sizeof(efs), (unsigned long) &efs);
|
|
|
|
}
|
2009-07-04 20:06:24 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MODE_STREAMING:
|
2011-10-17 11:19:59 +02:00
|
|
|
size = min_t(u16, le16_to_cpu(rfc.max_pdu_size),
|
|
|
|
chan->conn->mtu -
|
|
|
|
L2CAP_EXT_HDR_SIZE -
|
|
|
|
L2CAP_SDULEN_SIZE -
|
|
|
|
L2CAP_FCS_SIZE);
|
|
|
|
rfc.max_pdu_size = cpu_to_le16(size);
|
|
|
|
chan->remote_mps = size;
|
2009-07-04 20:06:24 +02:00
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_MODE_DONE, &chan->conf_state);
|
2009-10-18 02:41:01 +02:00
|
|
|
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
|
|
|
|
sizeof(rfc), (unsigned long) &rfc);
|
|
|
|
|
2009-07-04 20:06:24 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2007-05-24 14:27:19 +02:00
|
|
|
result = L2CAP_CONF_UNACCEPT;
|
|
|
|
|
2007-10-20 13:39:51 +02:00
|
|
|
memset(&rfc, 0, sizeof(rfc));
|
2011-04-13 22:20:49 +02:00
|
|
|
rfc.mode = chan->mode;
|
2009-07-04 20:06:24 +02:00
|
|
|
}
|
2007-10-20 13:39:51 +02:00
|
|
|
|
2009-07-04 20:06:24 +02:00
|
|
|
if (result == L2CAP_CONF_SUCCESS)
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_OUTPUT_DONE, &chan->conf_state);
|
2009-07-04 20:06:24 +02:00
|
|
|
}
|
2011-04-14 00:50:45 +02:00
|
|
|
rsp->scid = cpu_to_le16(chan->dcid);
|
2007-05-24 14:27:19 +02:00
|
|
|
rsp->result = cpu_to_le16(result);
|
|
|
|
rsp->flags = cpu_to_le16(0x0000);
|
|
|
|
|
|
|
|
return ptr - data;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2011-04-12 23:15:09 +02:00
|
|
|
static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len, void *data, u16 *result)
|
2009-07-04 20:06:24 +02:00
|
|
|
{
|
|
|
|
struct l2cap_conf_req *req = data;
|
|
|
|
void *ptr = req->data;
|
|
|
|
int type, olen;
|
|
|
|
unsigned long val;
|
|
|
|
struct l2cap_conf_rfc rfc;
|
2011-11-07 13:20:33 +01:00
|
|
|
struct l2cap_conf_efs efs;
|
2009-07-04 20:06:24 +02:00
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
BT_DBG("chan %p, rsp %p, len %d, req %p", chan, rsp, len, data);
|
2009-07-04 20:06:24 +02:00
|
|
|
|
|
|
|
while (len >= L2CAP_CONF_OPT_SIZE) {
|
|
|
|
len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case L2CAP_CONF_MTU:
|
|
|
|
if (val < L2CAP_DEFAULT_MIN_MTU) {
|
|
|
|
*result = L2CAP_CONF_UNACCEPT;
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->imtu = L2CAP_DEFAULT_MIN_MTU;
|
2009-07-04 20:06:24 +02:00
|
|
|
} else
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->imtu = val;
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, chan->imtu);
|
2009-07-04 20:06:24 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CONF_FLUSH_TO:
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->flush_to = val;
|
2009-07-04 20:06:24 +02:00
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO,
|
2011-04-13 22:20:49 +02:00
|
|
|
2, chan->flush_to);
|
2009-07-04 20:06:24 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CONF_RFC:
|
|
|
|
if (olen == sizeof(rfc))
|
|
|
|
memcpy(&rfc, (void *)val, olen);
|
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (test_bit(CONF_STATE2_DEVICE, &chan->conf_state) &&
|
2011-04-13 22:20:49 +02:00
|
|
|
rfc.mode != chan->mode)
|
2009-07-04 20:06:24 +02:00
|
|
|
return -ECONNREFUSED;
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->fcs = 0;
|
2009-07-04 20:06:24 +02:00
|
|
|
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
|
|
|
|
sizeof(rfc), (unsigned long) &rfc);
|
|
|
|
break;
|
2011-10-11 12:37:42 +02:00
|
|
|
|
|
|
|
case L2CAP_CONF_EWS:
|
|
|
|
chan->tx_win = min_t(u16, val,
|
|
|
|
L2CAP_DEFAULT_EXT_WINDOW);
|
2011-11-01 17:06:23 +01:00
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EWS, 2,
|
|
|
|
chan->tx_win);
|
2011-10-11 12:37:42 +02:00
|
|
|
break;
|
2011-11-07 13:20:33 +01:00
|
|
|
|
|
|
|
case L2CAP_CONF_EFS:
|
|
|
|
if (olen == sizeof(efs))
|
|
|
|
memcpy(&efs, (void *)val, olen);
|
|
|
|
|
|
|
|
if (chan->local_stype != L2CAP_SERV_NOTRAFIC &&
|
|
|
|
efs.stype != L2CAP_SERV_NOTRAFIC &&
|
|
|
|
efs.stype != chan->local_stype)
|
|
|
|
return -ECONNREFUSED;
|
|
|
|
|
|
|
|
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EFS,
|
|
|
|
sizeof(efs), (unsigned long) &efs);
|
|
|
|
break;
|
2009-07-04 20:06:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->mode == L2CAP_MODE_BASIC && chan->mode != rfc.mode)
|
2010-06-09 01:08:49 +02:00
|
|
|
return -ECONNREFUSED;
|
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->mode = rfc.mode;
|
2010-06-09 01:08:49 +02:00
|
|
|
|
Bluetooth: EFS: implement L2CAP config pending state
Add L2CAP Config Pending state for EFS. Currently after receiving
Config Response Pending respond with Config Response Success.
...
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0040 scid 0x0040 result 0 status 0
Connection successful
> ACL data: handle 1 flags 0x02 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 1009)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 510
L2CAP(d): cid 0x0040 len 506 ext_ctrl 0x00010000 fcs 0xebe0 [psm 4113]
I-frame: Start (len 672) TxSeq 0 ReqSeq 0
...
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-17 13:35:32 +02:00
|
|
|
if (*result == L2CAP_CONF_SUCCESS || *result == L2CAP_CONF_PENDING) {
|
2009-07-04 20:06:24 +02:00
|
|
|
switch (rfc.mode) {
|
|
|
|
case L2CAP_MODE_ERTM:
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->retrans_timeout = le16_to_cpu(rfc.retrans_timeout);
|
|
|
|
chan->monitor_timeout = le16_to_cpu(rfc.monitor_timeout);
|
|
|
|
chan->mps = le16_to_cpu(rfc.max_pdu_size);
|
2011-11-07 13:20:33 +01:00
|
|
|
|
|
|
|
if (test_bit(FLAG_EFS_ENABLE, &chan->flags)) {
|
|
|
|
chan->local_msdu = le16_to_cpu(efs.msdu);
|
|
|
|
chan->local_sdu_itime =
|
|
|
|
le32_to_cpu(efs.sdu_itime);
|
|
|
|
chan->local_acc_lat = le32_to_cpu(efs.acc_lat);
|
|
|
|
chan->local_flush_to =
|
|
|
|
le32_to_cpu(efs.flush_to);
|
|
|
|
}
|
2009-07-04 20:06:24 +02:00
|
|
|
break;
|
2011-11-07 13:20:33 +01:00
|
|
|
|
2009-07-04 20:06:24 +02:00
|
|
|
case L2CAP_MODE_STREAMING:
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->mps = le16_to_cpu(rfc.max_pdu_size);
|
2009-07-04 20:06:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
req->dcid = cpu_to_le16(chan->dcid);
|
2009-07-04 20:06:24 +02:00
|
|
|
req->flags = cpu_to_le16(0x0000);
|
|
|
|
|
|
|
|
return ptr - data;
|
|
|
|
}
|
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
static int l2cap_build_conf_rsp(struct l2cap_chan *chan, void *data, u16 result, u16 flags)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct l2cap_conf_rsp *rsp = data;
|
|
|
|
void *ptr = rsp->data;
|
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
BT_DBG("chan %p", chan);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
rsp->scid = cpu_to_le16(chan->dcid);
|
2007-05-24 14:27:19 +02:00
|
|
|
rsp->result = cpu_to_le16(result);
|
2007-03-26 05:12:50 +02:00
|
|
|
rsp->flags = cpu_to_le16(flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return ptr - data;
|
|
|
|
}
|
|
|
|
|
2011-04-14 01:23:55 +02:00
|
|
|
void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
|
2011-03-25 18:30:37 +01:00
|
|
|
{
|
|
|
|
struct l2cap_conn_rsp rsp;
|
2011-04-14 01:23:55 +02:00
|
|
|
struct l2cap_conn *conn = chan->conn;
|
2011-03-25 18:30:37 +01:00
|
|
|
u8 buf[128];
|
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
rsp.scid = cpu_to_le16(chan->dcid);
|
|
|
|
rsp.dcid = cpu_to_le16(chan->scid);
|
2011-03-25 18:30:37 +01:00
|
|
|
rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS);
|
|
|
|
rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO);
|
|
|
|
l2cap_send_cmd(conn, chan->ident,
|
|
|
|
L2CAP_CONN_RSP, sizeof(rsp), &rsp);
|
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (test_and_set_bit(CONF_REQ_SENT, &chan->conf_state))
|
2011-03-25 18:30:37 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
|
|
|
|
l2cap_build_conf_req(chan, buf), buf);
|
|
|
|
chan->num_conf_req++;
|
|
|
|
}
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
static void l2cap_conf_rfc_get(struct l2cap_chan *chan, void *rsp, int len)
|
2010-05-01 21:15:39 +02:00
|
|
|
{
|
|
|
|
int type, olen;
|
|
|
|
unsigned long val;
|
|
|
|
struct l2cap_conf_rfc rfc;
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
BT_DBG("chan %p, rsp %p, len %d", chan, rsp, len);
|
2010-05-01 21:15:39 +02:00
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
if ((chan->mode != L2CAP_MODE_ERTM) && (chan->mode != L2CAP_MODE_STREAMING))
|
2010-05-01 21:15:39 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
while (len >= L2CAP_CONF_OPT_SIZE) {
|
|
|
|
len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case L2CAP_CONF_RFC:
|
|
|
|
if (olen == sizeof(rfc))
|
|
|
|
memcpy(&rfc, (void *)val, olen);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
switch (rfc.mode) {
|
|
|
|
case L2CAP_MODE_ERTM:
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->retrans_timeout = le16_to_cpu(rfc.retrans_timeout);
|
|
|
|
chan->monitor_timeout = le16_to_cpu(rfc.monitor_timeout);
|
|
|
|
chan->mps = le16_to_cpu(rfc.max_pdu_size);
|
2010-05-01 21:15:39 +02:00
|
|
|
break;
|
|
|
|
case L2CAP_MODE_STREAMING:
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->mps = le16_to_cpu(rfc.max_pdu_size);
|
2010-05-01 21:15:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-10-20 13:37:56 +02:00
|
|
|
static inline int l2cap_command_rej(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
2011-07-10 07:47:44 +02:00
|
|
|
struct l2cap_cmd_rej_unk *rej = (struct l2cap_cmd_rej_unk *) data;
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2011-07-10 07:47:44 +02:00
|
|
|
if (rej->reason != L2CAP_REJ_NOT_UNDERSTOOD)
|
2007-10-20 13:37:56 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ((conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) &&
|
|
|
|
cmd->ident == conn->info_ident) {
|
|
|
|
del_timer(&conn->info_timer);
|
2009-02-06 23:35:19 +01:00
|
|
|
|
|
|
|
conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE;
|
2009-02-09 09:18:02 +01:00
|
|
|
conn->info_ident = 0;
|
2009-02-06 23:35:19 +01:00
|
|
|
|
2007-10-20 13:37:56 +02:00
|
|
|
l2cap_conn_start(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
|
|
|
struct l2cap_conn_req *req = (struct l2cap_conn_req *) data;
|
|
|
|
struct l2cap_conn_rsp rsp;
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *chan = NULL, *pchan;
|
2010-10-15 17:54:02 +02:00
|
|
|
struct sock *parent, *sk = NULL;
|
2008-09-09 07:19:20 +02:00
|
|
|
int result, status = L2CAP_CS_NO_INFO;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
u16 dcid = 0, scid = __le16_to_cpu(req->scid);
|
2008-09-09 07:19:20 +02:00
|
|
|
__le16 psm = req->psm;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
BT_DBG("psm 0x%2.2x scid 0x%4.4x", psm, scid);
|
|
|
|
|
|
|
|
/* Check if we have socket listening on psm */
|
2011-04-27 23:26:32 +02:00
|
|
|
pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, conn->src);
|
|
|
|
if (!pchan) {
|
2005-04-17 00:20:36 +02:00
|
|
|
result = L2CAP_CR_BAD_PSM;
|
|
|
|
goto sendresp;
|
|
|
|
}
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
parent = pchan->sk;
|
|
|
|
|
2010-11-01 19:43:53 +01:00
|
|
|
bh_lock_sock(parent);
|
|
|
|
|
2008-09-09 07:19:20 +02:00
|
|
|
/* Check if the ACL is secure enough (if not SDP) */
|
|
|
|
if (psm != cpu_to_le16(0x0001) &&
|
|
|
|
!hci_conn_check_link_mode(conn->hcon)) {
|
2011-11-07 13:20:25 +01:00
|
|
|
conn->disc_reason = HCI_ERROR_AUTH_FAILURE;
|
2008-09-09 07:19:20 +02:00
|
|
|
result = L2CAP_CR_SEC_BLOCK;
|
|
|
|
goto response;
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
result = L2CAP_CR_NO_MEM;
|
|
|
|
|
|
|
|
/* Check for backlog size */
|
|
|
|
if (sk_acceptq_is_full(parent)) {
|
2007-02-09 15:24:33 +01:00
|
|
|
BT_DBG("backlog full %d", parent->sk_ack_backlog);
|
2005-04-17 00:20:36 +02:00
|
|
|
goto response;
|
|
|
|
}
|
|
|
|
|
2011-05-16 22:24:37 +02:00
|
|
|
chan = pchan->ops->new_connection(pchan->data);
|
|
|
|
if (!chan)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto response;
|
|
|
|
|
2011-05-16 22:24:37 +02:00
|
|
|
sk = chan->sk;
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
write_lock_bh(&conn->chan_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Check if we already have channel with that dcid */
|
2011-03-31 21:17:41 +02:00
|
|
|
if (__l2cap_get_chan_by_dcid(conn, scid)) {
|
|
|
|
write_unlock_bh(&conn->chan_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
sock_set_flag(sk, SOCK_ZAPPED);
|
2011-05-16 23:23:24 +02:00
|
|
|
chan->ops->close(chan->data);
|
2005-04-17 00:20:36 +02:00
|
|
|
goto response;
|
|
|
|
}
|
|
|
|
|
|
|
|
hci_conn_hold(conn->hcon);
|
|
|
|
|
|
|
|
bacpy(&bt_sk(sk)->src, conn->src);
|
|
|
|
bacpy(&bt_sk(sk)->dst, conn->dst);
|
2011-04-14 00:50:45 +02:00
|
|
|
chan->psm = psm;
|
|
|
|
chan->dcid = scid;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-03-25 04:39:48 +01:00
|
|
|
bt_accept_enqueue(parent, sk);
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
__l2cap_chan_add(conn, chan);
|
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
dcid = chan->scid;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-05-17 19:59:01 +02:00
|
|
|
__set_chan_timer(chan, sk->sk_sndtimeo);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-03-25 17:59:37 +01:00
|
|
|
chan->ident = cmd->ident;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2009-02-06 23:35:19 +01:00
|
|
|
if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE) {
|
2011-11-05 22:54:24 +01:00
|
|
|
if (l2cap_chan_check_security(chan)) {
|
2009-01-15 21:57:00 +01:00
|
|
|
if (bt_sk(sk)->defer_setup) {
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECT2);
|
2009-01-15 21:57:00 +01:00
|
|
|
result = L2CAP_CR_PEND;
|
|
|
|
status = L2CAP_CS_AUTHOR_PEND;
|
|
|
|
parent->sk_data_ready(parent, 0);
|
|
|
|
} else {
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONFIG);
|
2009-01-15 21:57:00 +01:00
|
|
|
result = L2CAP_CR_SUCCESS;
|
|
|
|
status = L2CAP_CS_NO_INFO;
|
|
|
|
}
|
2008-07-14 20:13:44 +02:00
|
|
|
} else {
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECT2);
|
2008-07-14 20:13:44 +02:00
|
|
|
result = L2CAP_CR_PEND;
|
|
|
|
status = L2CAP_CS_AUTHEN_PEND;
|
|
|
|
}
|
|
|
|
} else {
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECT2);
|
2008-07-14 20:13:44 +02:00
|
|
|
result = L2CAP_CR_PEND;
|
|
|
|
status = L2CAP_CS_NO_INFO;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
write_unlock_bh(&conn->chan_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
response:
|
|
|
|
bh_unlock_sock(parent);
|
|
|
|
|
|
|
|
sendresp:
|
2007-03-26 05:12:50 +02:00
|
|
|
rsp.scid = cpu_to_le16(scid);
|
|
|
|
rsp.dcid = cpu_to_le16(dcid);
|
|
|
|
rsp.result = cpu_to_le16(result);
|
|
|
|
rsp.status = cpu_to_le16(status);
|
2005-04-17 00:20:36 +02:00
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_CONN_RSP, sizeof(rsp), &rsp);
|
2008-07-14 20:13:44 +02:00
|
|
|
|
|
|
|
if (result == L2CAP_CR_PEND && status == L2CAP_CS_NO_INFO) {
|
|
|
|
struct l2cap_info_req info;
|
|
|
|
info.type = cpu_to_le16(L2CAP_IT_FEAT_MASK);
|
|
|
|
|
|
|
|
conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_SENT;
|
|
|
|
conn->info_ident = l2cap_get_ident(conn);
|
|
|
|
|
|
|
|
mod_timer(&conn->info_timer, jiffies +
|
|
|
|
msecs_to_jiffies(L2CAP_INFO_TIMEOUT));
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, conn->info_ident,
|
|
|
|
L2CAP_INFO_REQ, sizeof(info), &info);
|
|
|
|
}
|
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (chan && !test_bit(CONF_REQ_SENT, &chan->conf_state) &&
|
2010-07-09 01:08:18 +02:00
|
|
|
result == L2CAP_CR_SUCCESS) {
|
|
|
|
u8 buf[128];
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_REQ_SENT, &chan->conf_state);
|
2010-07-09 01:08:18 +02:00
|
|
|
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
|
2011-03-25 18:16:54 +01:00
|
|
|
l2cap_build_conf_req(chan, buf), buf);
|
|
|
|
chan->num_conf_req++;
|
2010-07-09 01:08:18 +02:00
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_connect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
|
|
|
struct l2cap_conn_rsp *rsp = (struct l2cap_conn_rsp *) data;
|
|
|
|
u16 scid, dcid, result, status;
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2005-04-17 00:20:36 +02:00
|
|
|
struct sock *sk;
|
|
|
|
u8 req[128];
|
|
|
|
|
|
|
|
scid = __le16_to_cpu(rsp->scid);
|
|
|
|
dcid = __le16_to_cpu(rsp->dcid);
|
|
|
|
result = __le16_to_cpu(rsp->result);
|
|
|
|
status = __le16_to_cpu(rsp->status);
|
|
|
|
|
|
|
|
BT_DBG("dcid 0x%4.4x scid 0x%4.4x result 0x%2.2x status 0x%2.2x", dcid, scid, result, status);
|
|
|
|
|
|
|
|
if (scid) {
|
2011-03-31 21:17:41 +02:00
|
|
|
chan = l2cap_get_chan_by_scid(conn, scid);
|
2011-03-25 04:22:30 +01:00
|
|
|
if (!chan)
|
2010-06-22 18:56:26 +02:00
|
|
|
return -EFAULT;
|
2005-04-17 00:20:36 +02:00
|
|
|
} else {
|
2011-03-31 21:17:41 +02:00
|
|
|
chan = l2cap_get_chan_by_ident(conn, cmd->ident);
|
2011-03-25 04:22:30 +01:00
|
|
|
if (!chan)
|
2010-06-22 18:56:26 +02:00
|
|
|
return -EFAULT;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
sk = chan->sk;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
switch (result) {
|
|
|
|
case L2CAP_CR_SUCCESS:
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONFIG);
|
2011-03-25 17:59:37 +01:00
|
|
|
chan->ident = 0;
|
2011-04-14 00:50:45 +02:00
|
|
|
chan->dcid = dcid;
|
2011-06-10 22:02:12 +02:00
|
|
|
clear_bit(CONF_CONNECT_PEND, &chan->conf_state);
|
2009-02-06 23:56:36 +01:00
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (test_and_set_bit(CONF_REQ_SENT, &chan->conf_state))
|
2010-07-09 01:08:18 +02:00
|
|
|
break;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
|
2011-03-25 18:16:54 +01:00
|
|
|
l2cap_build_conf_req(chan, req), req);
|
|
|
|
chan->num_conf_req++;
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CR_PEND:
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_CONNECT_PEND, &chan->conf_state);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2010-11-03 11:32:44 +01:00
|
|
|
/* don't delete l2cap channel if sk is owned by user */
|
|
|
|
if (sock_owned_by_user(sk)) {
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_DISCONN);
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2011-11-07 20:19:04 +01:00
|
|
|
__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
|
2010-11-03 11:32:44 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
l2cap_chan_del(chan, ECONNREFUSED);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
static inline void set_default_fcs(struct l2cap_chan *chan)
|
2010-08-25 00:35:42 +02:00
|
|
|
{
|
|
|
|
/* FCS is enabled only in ERTM or streaming mode, if one or both
|
|
|
|
* sides request it.
|
|
|
|
*/
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->mode != L2CAP_MODE_ERTM && chan->mode != L2CAP_MODE_STREAMING)
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->fcs = L2CAP_FCS_NONE;
|
2011-06-10 22:02:12 +02:00
|
|
|
else if (!test_bit(CONF_NO_FCS_RECV, &chan->conf_state))
|
2011-04-13 20:57:03 +02:00
|
|
|
chan->fcs = L2CAP_FCS_CRC16;
|
2010-08-25 00:35:42 +02:00
|
|
|
}
|
|
|
|
|
2007-07-29 09:17:25 +02:00
|
|
|
static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct l2cap_conf_req *req = (struct l2cap_conf_req *) data;
|
|
|
|
u16 dcid, flags;
|
|
|
|
u8 rsp[64];
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2005-04-17 00:20:36 +02:00
|
|
|
struct sock *sk;
|
2007-05-24 14:27:19 +02:00
|
|
|
int len;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
dcid = __le16_to_cpu(req->dcid);
|
|
|
|
flags = __le16_to_cpu(req->flags);
|
|
|
|
|
|
|
|
BT_DBG("dcid 0x%4.4x flags 0x%2.2x", dcid, flags);
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
chan = l2cap_get_chan_by_scid(conn, dcid);
|
2011-03-25 04:22:30 +01:00
|
|
|
if (!chan)
|
2005-04-17 00:20:36 +02:00
|
|
|
return -ENOENT;
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
sk = chan->sk;
|
|
|
|
|
2011-07-21 22:38:42 +02:00
|
|
|
if (chan->state != BT_CONFIG && chan->state != BT_CONNECT2) {
|
2011-07-10 07:47:44 +02:00
|
|
|
struct l2cap_cmd_rej_cid rej;
|
|
|
|
|
|
|
|
rej.reason = cpu_to_le16(L2CAP_REJ_INVALID_CID);
|
|
|
|
rej.scid = cpu_to_le16(chan->scid);
|
|
|
|
rej.dcid = cpu_to_le16(chan->dcid);
|
2010-06-14 07:26:15 +02:00
|
|
|
|
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_COMMAND_REJ,
|
|
|
|
sizeof(rej), &rej);
|
2006-11-18 22:15:20 +01:00
|
|
|
goto unlock;
|
2010-06-14 07:26:15 +02:00
|
|
|
}
|
2006-11-18 22:15:20 +01:00
|
|
|
|
2007-05-24 14:27:19 +02:00
|
|
|
/* Reject if config buffer is too small. */
|
2007-07-29 09:17:25 +02:00
|
|
|
len = cmd_len - sizeof(*req);
|
2011-06-24 14:38:05 +02:00
|
|
|
if (len < 0 || chan->conf_len + len > sizeof(chan->conf_req)) {
|
2007-05-24 14:27:19 +02:00
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP,
|
2011-04-14 00:50:45 +02:00
|
|
|
l2cap_build_conf_rsp(chan, rsp,
|
2007-05-24 14:27:19 +02:00
|
|
|
L2CAP_CONF_REJECT, flags), rsp);
|
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Store config. */
|
2011-03-25 18:16:54 +01:00
|
|
|
memcpy(chan->conf_req + chan->conf_len, req->data, len);
|
|
|
|
chan->conf_len += len;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (flags & 0x0001) {
|
|
|
|
/* Incomplete config. Send empty response. */
|
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP,
|
2011-04-14 00:50:45 +02:00
|
|
|
l2cap_build_conf_rsp(chan, rsp,
|
2007-05-24 14:27:19 +02:00
|
|
|
L2CAP_CONF_SUCCESS, 0x0001), rsp);
|
2005-04-17 00:20:36 +02:00
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Complete config. */
|
2011-03-25 18:16:54 +01:00
|
|
|
len = l2cap_parse_conf_req(chan, rsp);
|
2009-07-04 20:06:24 +02:00
|
|
|
if (len < 0) {
|
2011-04-01 05:53:45 +02:00
|
|
|
l2cap_send_disconn_req(conn, chan, ECONNRESET);
|
2005-04-17 00:20:36 +02:00
|
|
|
goto unlock;
|
2009-07-04 20:06:24 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-05-24 14:27:19 +02:00
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, len, rsp);
|
2011-03-25 18:16:54 +01:00
|
|
|
chan->num_conf_rsp++;
|
2007-05-24 14:27:19 +02:00
|
|
|
|
|
|
|
/* Reset config buffer. */
|
2011-03-25 18:16:54 +01:00
|
|
|
chan->conf_len = 0;
|
2007-05-24 14:27:19 +02:00
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (!test_bit(CONF_OUTPUT_DONE, &chan->conf_state))
|
2007-10-20 13:35:42 +02:00
|
|
|
goto unlock;
|
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (test_bit(CONF_INPUT_DONE, &chan->conf_state)) {
|
2011-04-13 20:57:03 +02:00
|
|
|
set_default_fcs(chan);
|
2009-08-21 03:26:02 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECTED);
|
2009-08-21 03:26:00 +02:00
|
|
|
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->next_tx_seq = 0;
|
|
|
|
chan->expected_tx_seq = 0;
|
2011-04-04 21:16:44 +02:00
|
|
|
skb_queue_head_init(&chan->tx_q);
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->mode == L2CAP_MODE_ERTM)
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_ertm_init(chan);
|
2009-10-03 07:34:36 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
l2cap_chan_ready(sk);
|
2007-10-20 13:35:42 +02:00
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (!test_and_set_bit(CONF_REQ_SENT, &chan->conf_state)) {
|
2008-07-14 20:13:44 +02:00
|
|
|
u8 buf[64];
|
2005-04-17 00:20:36 +02:00
|
|
|
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
|
2011-03-25 18:16:54 +01:00
|
|
|
l2cap_build_conf_req(chan, buf), buf);
|
|
|
|
chan->num_conf_req++;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
Bluetooth: EFS: implement L2CAP config pending state
Add L2CAP Config Pending state for EFS. Currently after receiving
Config Response Pending respond with Config Response Success.
...
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0040 scid 0x0040 result 0 status 0
Connection successful
> ACL data: handle 1 flags 0x02 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 1009)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 510
L2CAP(d): cid 0x0040 len 506 ext_ctrl 0x00010000 fcs 0xebe0 [psm 4113]
I-frame: Start (len 672) TxSeq 0 ReqSeq 0
...
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-17 13:35:32 +02:00
|
|
|
/* Got Conf Rsp PENDING from remote side and asume we sent
|
|
|
|
Conf Rsp PENDING in the code above */
|
|
|
|
if (test_bit(CONF_REM_CONF_PEND, &chan->conf_state) &&
|
|
|
|
test_bit(CONF_LOC_CONF_PEND, &chan->conf_state)) {
|
|
|
|
|
|
|
|
/* check compatibility */
|
|
|
|
|
|
|
|
clear_bit(CONF_LOC_CONF_PEND, &chan->conf_state);
|
|
|
|
set_bit(CONF_OUTPUT_DONE, &chan->conf_state);
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP,
|
2011-11-01 17:06:23 +01:00
|
|
|
l2cap_build_conf_rsp(chan, rsp,
|
Bluetooth: EFS: implement L2CAP config pending state
Add L2CAP Config Pending state for EFS. Currently after receiving
Config Response Pending respond with Config Response Success.
...
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0040 scid 0x0040 result 0 status 0
Connection successful
> ACL data: handle 1 flags 0x02 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 1009)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 510
L2CAP(d): cid 0x0040 len 506 ext_ctrl 0x00010000 fcs 0xebe0 [psm 4113]
I-frame: Start (len 672) TxSeq 0 ReqSeq 0
...
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-17 13:35:32 +02:00
|
|
|
L2CAP_CONF_SUCCESS, 0x0000), rsp);
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
unlock:
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_config_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
|
|
|
struct l2cap_conf_rsp *rsp = (struct l2cap_conf_rsp *)data;
|
|
|
|
u16 scid, flags, result;
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2005-04-17 00:20:36 +02:00
|
|
|
struct sock *sk;
|
2010-05-01 21:15:39 +02:00
|
|
|
int len = cmd->len - sizeof(*rsp);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
scid = __le16_to_cpu(rsp->scid);
|
|
|
|
flags = __le16_to_cpu(rsp->flags);
|
|
|
|
result = __le16_to_cpu(rsp->result);
|
|
|
|
|
2009-04-20 06:31:08 +02:00
|
|
|
BT_DBG("scid 0x%4.4x flags 0x%2.2x result 0x%2.2x",
|
|
|
|
scid, flags, result);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
chan = l2cap_get_chan_by_scid(conn, scid);
|
2011-03-25 04:22:30 +01:00
|
|
|
if (!chan)
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
sk = chan->sk;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
switch (result) {
|
|
|
|
case L2CAP_CONF_SUCCESS:
|
2011-04-13 20:57:03 +02:00
|
|
|
l2cap_conf_rfc_get(chan, rsp->data, len);
|
Bluetooth: EFS: implement L2CAP config pending state
Add L2CAP Config Pending state for EFS. Currently after receiving
Config Response Pending respond with Config Response Success.
...
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0040 scid 0x0040 result 0 status 0
Connection successful
> ACL data: handle 1 flags 0x02 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 1009)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 510
L2CAP(d): cid 0x0040 len 506 ext_ctrl 0x00010000 fcs 0xebe0 [psm 4113]
I-frame: Start (len 672) TxSeq 0 ReqSeq 0
...
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-17 13:35:32 +02:00
|
|
|
clear_bit(CONF_REM_CONF_PEND, &chan->conf_state);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
|
Bluetooth: EFS: implement L2CAP config pending state
Add L2CAP Config Pending state for EFS. Currently after receiving
Config Response Pending respond with Config Response Success.
...
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0040 scid 0x0040 result 0 status 0
Connection successful
> ACL data: handle 1 flags 0x02 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 1009)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 510
L2CAP(d): cid 0x0040 len 506 ext_ctrl 0x00010000 fcs 0xebe0 [psm 4113]
I-frame: Start (len 672) TxSeq 0 ReqSeq 0
...
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-17 13:35:32 +02:00
|
|
|
case L2CAP_CONF_PENDING:
|
|
|
|
set_bit(CONF_REM_CONF_PEND, &chan->conf_state);
|
|
|
|
|
|
|
|
if (test_bit(CONF_LOC_CONF_PEND, &chan->conf_state)) {
|
|
|
|
char buf[64];
|
|
|
|
|
|
|
|
len = l2cap_parse_conf_rsp(chan, rsp->data, len,
|
|
|
|
buf, &result);
|
|
|
|
if (len < 0) {
|
|
|
|
l2cap_send_disconn_req(conn, chan, ECONNRESET);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check compatibility */
|
|
|
|
|
|
|
|
clear_bit(CONF_LOC_CONF_PEND, &chan->conf_state);
|
|
|
|
set_bit(CONF_OUTPUT_DONE, &chan->conf_state);
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP,
|
2011-11-01 17:06:23 +01:00
|
|
|
l2cap_build_conf_rsp(chan, buf,
|
Bluetooth: EFS: implement L2CAP config pending state
Add L2CAP Config Pending state for EFS. Currently after receiving
Config Response Pending respond with Config Response Success.
...
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0040 scid 0x0040 result 0 status 0
Connection successful
> ACL data: handle 1 flags 0x02 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 1009)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 45
L2CAP(s): Config req: dcid 0x0040 flags 0x00 clen 33
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 0, MTo 0, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
< ACL data: handle 1 flags 0x00 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 47
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 4 clen 33
Pending
MTU 672
RFC 0x03 (Enhanced Retransmission, TxWin 63, MaxTx 3, RTo 2000, MTo 12000, MPS 498)
EFS (Id 0x01, SerType Best Effort, MaxSDU 0xffff, SDUitime 0xffffffff,
AccLat 0xffffffff, FlushTO 0x0000ffff)
> ACL data: handle 1 flags 0x02 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 14
L2CAP(s): Config rsp: scid 0x0040 flags 0x00 result 0 clen 0
Success
< ACL data: handle 1 flags 0x00 dlen 510
L2CAP(d): cid 0x0040 len 506 ext_ctrl 0x00010000 fcs 0xebe0 [psm 4113]
I-frame: Start (len 672) TxSeq 0 ReqSeq 0
...
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
2011-10-17 13:35:32 +02:00
|
|
|
L2CAP_CONF_SUCCESS, 0x0000), buf);
|
|
|
|
}
|
|
|
|
goto done;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
case L2CAP_CONF_UNACCEPT:
|
2011-03-25 18:16:54 +01:00
|
|
|
if (chan->num_conf_rsp <= L2CAP_CONF_MAX_CONF_RSP) {
|
2009-07-04 20:06:24 +02:00
|
|
|
char req[64];
|
|
|
|
|
2010-03-19 09:26:28 +01:00
|
|
|
if (len > sizeof(req) - sizeof(struct l2cap_conf_req)) {
|
2011-04-01 05:53:45 +02:00
|
|
|
l2cap_send_disconn_req(conn, chan, ECONNRESET);
|
2010-03-19 09:26:28 +01:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2009-07-04 20:06:24 +02:00
|
|
|
/* throw out any old stored conf requests */
|
|
|
|
result = L2CAP_CONF_SUCCESS;
|
2011-04-12 23:15:09 +02:00
|
|
|
len = l2cap_parse_conf_rsp(chan, rsp->data, len,
|
|
|
|
req, &result);
|
2009-07-04 20:06:24 +02:00
|
|
|
if (len < 0) {
|
2011-04-01 05:53:45 +02:00
|
|
|
l2cap_send_disconn_req(conn, chan, ECONNRESET);
|
2009-07-04 20:06:24 +02:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, l2cap_get_ident(conn),
|
|
|
|
L2CAP_CONF_REQ, len, req);
|
2011-03-25 18:16:54 +01:00
|
|
|
chan->num_conf_req++;
|
2009-07-04 20:06:24 +02:00
|
|
|
if (result != L2CAP_CONF_SUCCESS)
|
|
|
|
goto done;
|
|
|
|
break;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2007-02-09 15:24:33 +01:00
|
|
|
default:
|
2008-07-14 20:13:54 +02:00
|
|
|
sk->sk_err = ECONNRESET;
|
2011-11-07 20:19:04 +01:00
|
|
|
__set_chan_timer(chan, L2CAP_DISC_REJ_TIMEOUT);
|
2011-04-01 05:53:45 +02:00
|
|
|
l2cap_send_disconn_req(conn, chan, ECONNRESET);
|
2005-04-17 00:20:36 +02:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & 0x01)
|
|
|
|
goto done;
|
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_INPUT_DONE, &chan->conf_state);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (test_bit(CONF_OUTPUT_DONE, &chan->conf_state)) {
|
2011-04-13 20:57:03 +02:00
|
|
|
set_default_fcs(chan);
|
2009-08-21 03:26:02 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_CONNECTED);
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->next_tx_seq = 0;
|
|
|
|
chan->expected_tx_seq = 0;
|
2011-04-04 21:16:44 +02:00
|
|
|
skb_queue_head_init(&chan->tx_q);
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->mode == L2CAP_MODE_ERTM)
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_ertm_init(chan);
|
2009-10-03 07:34:36 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
l2cap_chan_ready(sk);
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_disconnect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
|
|
|
struct l2cap_disconn_req *req = (struct l2cap_disconn_req *) data;
|
|
|
|
struct l2cap_disconn_rsp rsp;
|
|
|
|
u16 dcid, scid;
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2005-04-17 00:20:36 +02:00
|
|
|
struct sock *sk;
|
|
|
|
|
|
|
|
scid = __le16_to_cpu(req->scid);
|
|
|
|
dcid = __le16_to_cpu(req->dcid);
|
|
|
|
|
|
|
|
BT_DBG("scid 0x%4.4x dcid 0x%4.4x", scid, dcid);
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
chan = l2cap_get_chan_by_scid(conn, dcid);
|
2011-03-25 04:22:30 +01:00
|
|
|
if (!chan)
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
sk = chan->sk;
|
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
rsp.dcid = cpu_to_le16(chan->scid);
|
|
|
|
rsp.scid = cpu_to_le16(chan->dcid);
|
2005-04-17 00:20:36 +02:00
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_DISCONN_RSP, sizeof(rsp), &rsp);
|
|
|
|
|
|
|
|
sk->sk_shutdown = SHUTDOWN_MASK;
|
|
|
|
|
2010-11-03 11:32:44 +01:00
|
|
|
/* don't delete l2cap channel if sk is owned by user */
|
|
|
|
if (sock_owned_by_user(sk)) {
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_DISCONN);
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2011-11-07 20:19:04 +01:00
|
|
|
__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
|
2010-11-03 11:32:44 +01:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
l2cap_chan_del(chan, ECONNRESET);
|
2005-04-17 00:20:36 +02:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
|
2011-05-16 23:23:24 +02:00
|
|
|
chan->ops->close(chan->data);
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
|
|
|
struct l2cap_disconn_rsp *rsp = (struct l2cap_disconn_rsp *) data;
|
|
|
|
u16 dcid, scid;
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2005-04-17 00:20:36 +02:00
|
|
|
struct sock *sk;
|
|
|
|
|
|
|
|
scid = __le16_to_cpu(rsp->scid);
|
|
|
|
dcid = __le16_to_cpu(rsp->dcid);
|
|
|
|
|
|
|
|
BT_DBG("dcid 0x%4.4x scid 0x%4.4x", dcid, scid);
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
chan = l2cap_get_chan_by_scid(conn, scid);
|
2011-03-25 04:22:30 +01:00
|
|
|
if (!chan)
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
sk = chan->sk;
|
|
|
|
|
2010-11-03 11:32:44 +01:00
|
|
|
/* don't delete l2cap channel if sk is owned by user */
|
|
|
|
if (sock_owned_by_user(sk)) {
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan,BT_DISCONN);
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2011-11-07 20:19:04 +01:00
|
|
|
__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
|
2010-11-03 11:32:44 +01:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
l2cap_chan_del(chan, 0);
|
2005-04-17 00:20:36 +02:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
|
2011-05-16 23:23:24 +02:00
|
|
|
chan->ops->close(chan->data);
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_information_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
|
|
|
struct l2cap_info_req *req = (struct l2cap_info_req *) data;
|
|
|
|
u16 type;
|
|
|
|
|
|
|
|
type = __le16_to_cpu(req->type);
|
|
|
|
|
|
|
|
BT_DBG("type 0x%4.4x", type);
|
|
|
|
|
2007-10-20 13:38:51 +02:00
|
|
|
if (type == L2CAP_IT_FEAT_MASK) {
|
|
|
|
u8 buf[8];
|
2009-05-03 04:09:01 +02:00
|
|
|
u32 feat_mask = l2cap_feat_mask;
|
2007-10-20 13:38:51 +02:00
|
|
|
struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) buf;
|
|
|
|
rsp->type = cpu_to_le16(L2CAP_IT_FEAT_MASK);
|
|
|
|
rsp->result = cpu_to_le16(L2CAP_IR_SUCCESS);
|
2010-07-18 21:25:54 +02:00
|
|
|
if (!disable_ertm)
|
2009-08-21 03:26:02 +02:00
|
|
|
feat_mask |= L2CAP_FEAT_ERTM | L2CAP_FEAT_STREAMING
|
|
|
|
| L2CAP_FEAT_FCS;
|
2011-09-16 15:26:32 +02:00
|
|
|
if (enable_hs)
|
2011-10-11 12:37:42 +02:00
|
|
|
feat_mask |= L2CAP_FEAT_EXT_FLOW
|
|
|
|
| L2CAP_FEAT_EXT_WINDOW;
|
2011-09-16 15:26:32 +02:00
|
|
|
|
2009-08-24 05:45:20 +02:00
|
|
|
put_unaligned_le32(feat_mask, rsp->data);
|
2007-10-20 13:38:51 +02:00
|
|
|
l2cap_send_cmd(conn, cmd->ident,
|
|
|
|
L2CAP_INFO_RSP, sizeof(buf), buf);
|
2009-02-09 09:18:02 +01:00
|
|
|
} else if (type == L2CAP_IT_FIXED_CHAN) {
|
|
|
|
u8 buf[12];
|
|
|
|
struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) buf;
|
2011-11-03 00:18:34 +01:00
|
|
|
|
|
|
|
if (enable_hs)
|
|
|
|
l2cap_fixed_chan[0] |= L2CAP_FC_A2MP;
|
|
|
|
else
|
|
|
|
l2cap_fixed_chan[0] &= ~L2CAP_FC_A2MP;
|
|
|
|
|
2009-02-09 09:18:02 +01:00
|
|
|
rsp->type = cpu_to_le16(L2CAP_IT_FIXED_CHAN);
|
|
|
|
rsp->result = cpu_to_le16(L2CAP_IR_SUCCESS);
|
2011-10-20 16:02:44 +02:00
|
|
|
memcpy(rsp->data, l2cap_fixed_chan, sizeof(l2cap_fixed_chan));
|
2009-02-09 09:18:02 +01:00
|
|
|
l2cap_send_cmd(conn, cmd->ident,
|
|
|
|
L2CAP_INFO_RSP, sizeof(buf), buf);
|
2007-10-20 13:38:51 +02:00
|
|
|
} else {
|
|
|
|
struct l2cap_info_rsp rsp;
|
|
|
|
rsp.type = cpu_to_le16(type);
|
|
|
|
rsp.result = cpu_to_le16(L2CAP_IR_NOTSUPP);
|
|
|
|
l2cap_send_cmd(conn, cmd->ident,
|
|
|
|
L2CAP_INFO_RSP, sizeof(rsp), &rsp);
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_information_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
|
|
|
struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) data;
|
|
|
|
u16 type, result;
|
|
|
|
|
|
|
|
type = __le16_to_cpu(rsp->type);
|
|
|
|
result = __le16_to_cpu(rsp->result);
|
|
|
|
|
|
|
|
BT_DBG("type 0x%4.4x result 0x%2.2x", type, result);
|
|
|
|
|
2011-03-25 10:31:41 +01:00
|
|
|
/* L2CAP Info req/rsp are unbound to channels, add extra checks */
|
|
|
|
if (cmd->ident != conn->info_ident ||
|
|
|
|
conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE)
|
|
|
|
return 0;
|
|
|
|
|
2007-10-20 13:37:56 +02:00
|
|
|
del_timer(&conn->info_timer);
|
|
|
|
|
2010-08-04 08:43:33 +02:00
|
|
|
if (result != L2CAP_IR_SUCCESS) {
|
|
|
|
conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE;
|
|
|
|
conn->info_ident = 0;
|
|
|
|
|
|
|
|
l2cap_conn_start(conn);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-02-06 23:35:19 +01:00
|
|
|
if (type == L2CAP_IT_FEAT_MASK) {
|
2008-05-03 01:25:46 +02:00
|
|
|
conn->feat_mask = get_unaligned_le32(rsp->data);
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2009-05-03 03:57:55 +02:00
|
|
|
if (conn->feat_mask & L2CAP_FEAT_FIXED_CHAN) {
|
2009-02-09 09:18:02 +01:00
|
|
|
struct l2cap_info_req req;
|
|
|
|
req.type = cpu_to_le16(L2CAP_IT_FIXED_CHAN);
|
|
|
|
|
|
|
|
conn->info_ident = l2cap_get_ident(conn);
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, conn->info_ident,
|
|
|
|
L2CAP_INFO_REQ, sizeof(req), &req);
|
|
|
|
} else {
|
|
|
|
conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE;
|
|
|
|
conn->info_ident = 0;
|
|
|
|
|
|
|
|
l2cap_conn_start(conn);
|
|
|
|
}
|
|
|
|
} else if (type == L2CAP_IT_FIXED_CHAN) {
|
2009-02-06 23:35:19 +01:00
|
|
|
conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE;
|
2009-02-09 09:18:02 +01:00
|
|
|
conn->info_ident = 0;
|
2009-02-06 23:35:19 +01:00
|
|
|
|
|
|
|
l2cap_conn_start(conn);
|
|
|
|
}
|
2007-10-20 13:37:56 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-11-03 00:18:32 +01:00
|
|
|
static inline int l2cap_create_channel_req(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct l2cap_create_chan_req *req = data;
|
|
|
|
struct l2cap_create_chan_rsp rsp;
|
|
|
|
u16 psm, scid;
|
|
|
|
|
|
|
|
if (cmd_len != sizeof(*req))
|
|
|
|
return -EPROTO;
|
|
|
|
|
|
|
|
if (!enable_hs)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
psm = le16_to_cpu(req->psm);
|
|
|
|
scid = le16_to_cpu(req->scid);
|
|
|
|
|
|
|
|
BT_DBG("psm %d, scid %d, amp_id %d", psm, scid, req->amp_id);
|
|
|
|
|
|
|
|
/* Placeholder: Always reject */
|
|
|
|
rsp.dcid = 0;
|
|
|
|
rsp.scid = cpu_to_le16(scid);
|
|
|
|
rsp.result = L2CAP_CR_NO_MEM;
|
|
|
|
rsp.status = L2CAP_CS_NO_INFO;
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_CREATE_CHAN_RSP,
|
|
|
|
sizeof(rsp), &rsp);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_create_channel_rsp(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_cmd_hdr *cmd, void *data)
|
|
|
|
{
|
|
|
|
BT_DBG("conn %p", conn);
|
|
|
|
|
|
|
|
return l2cap_connect_rsp(conn, cmd, data);
|
|
|
|
}
|
|
|
|
|
2011-11-03 00:18:35 +01:00
|
|
|
static void l2cap_send_move_chan_rsp(struct l2cap_conn *conn, u8 ident,
|
|
|
|
u16 icid, u16 result)
|
|
|
|
{
|
|
|
|
struct l2cap_move_chan_rsp rsp;
|
|
|
|
|
|
|
|
BT_DBG("icid %d, result %d", icid, result);
|
|
|
|
|
|
|
|
rsp.icid = cpu_to_le16(icid);
|
|
|
|
rsp.result = cpu_to_le16(result);
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_RSP, sizeof(rsp), &rsp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_send_move_chan_cfm(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_chan *chan, u16 icid, u16 result)
|
|
|
|
{
|
|
|
|
struct l2cap_move_chan_cfm cfm;
|
|
|
|
u8 ident;
|
|
|
|
|
|
|
|
BT_DBG("icid %d, result %d", icid, result);
|
|
|
|
|
|
|
|
ident = l2cap_get_ident(conn);
|
|
|
|
if (chan)
|
|
|
|
chan->ident = ident;
|
|
|
|
|
|
|
|
cfm.icid = cpu_to_le16(icid);
|
|
|
|
cfm.result = cpu_to_le16(result);
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM, sizeof(cfm), &cfm);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_send_move_chan_cfm_rsp(struct l2cap_conn *conn, u8 ident,
|
|
|
|
u16 icid)
|
|
|
|
{
|
|
|
|
struct l2cap_move_chan_cfm_rsp rsp;
|
|
|
|
|
|
|
|
BT_DBG("icid %d", icid);
|
|
|
|
|
|
|
|
rsp.icid = cpu_to_le16(icid);
|
|
|
|
l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM_RSP, sizeof(rsp), &rsp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_move_channel_req(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data)
|
|
|
|
{
|
|
|
|
struct l2cap_move_chan_req *req = data;
|
|
|
|
u16 icid = 0;
|
|
|
|
u16 result = L2CAP_MR_NOT_ALLOWED;
|
|
|
|
|
|
|
|
if (cmd_len != sizeof(*req))
|
|
|
|
return -EPROTO;
|
|
|
|
|
|
|
|
icid = le16_to_cpu(req->icid);
|
|
|
|
|
|
|
|
BT_DBG("icid %d, dest_amp_id %d", icid, req->dest_amp_id);
|
|
|
|
|
|
|
|
if (!enable_hs)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* Placeholder: Always refuse */
|
|
|
|
l2cap_send_move_chan_rsp(conn, cmd->ident, icid, result);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_move_channel_rsp(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data)
|
|
|
|
{
|
|
|
|
struct l2cap_move_chan_rsp *rsp = data;
|
|
|
|
u16 icid, result;
|
|
|
|
|
|
|
|
if (cmd_len != sizeof(*rsp))
|
|
|
|
return -EPROTO;
|
|
|
|
|
|
|
|
icid = le16_to_cpu(rsp->icid);
|
|
|
|
result = le16_to_cpu(rsp->result);
|
|
|
|
|
|
|
|
BT_DBG("icid %d, result %d", icid, result);
|
|
|
|
|
|
|
|
/* Placeholder: Always unconfirmed */
|
|
|
|
l2cap_send_move_chan_cfm(conn, NULL, icid, L2CAP_MC_UNCONFIRMED);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_move_channel_confirm(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data)
|
|
|
|
{
|
|
|
|
struct l2cap_move_chan_cfm *cfm = data;
|
|
|
|
u16 icid, result;
|
|
|
|
|
|
|
|
if (cmd_len != sizeof(*cfm))
|
|
|
|
return -EPROTO;
|
|
|
|
|
|
|
|
icid = le16_to_cpu(cfm->icid);
|
|
|
|
result = le16_to_cpu(cfm->result);
|
|
|
|
|
|
|
|
BT_DBG("icid %d, result %d", icid, result);
|
|
|
|
|
|
|
|
l2cap_send_move_chan_cfm_rsp(conn, cmd->ident, icid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_move_channel_confirm_rsp(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data)
|
|
|
|
{
|
|
|
|
struct l2cap_move_chan_cfm_rsp *rsp = data;
|
|
|
|
u16 icid;
|
|
|
|
|
|
|
|
if (cmd_len != sizeof(*rsp))
|
|
|
|
return -EPROTO;
|
|
|
|
|
|
|
|
icid = le16_to_cpu(rsp->icid);
|
|
|
|
|
|
|
|
BT_DBG("icid %d", icid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-02-17 23:16:55 +01:00
|
|
|
static inline int l2cap_check_conn_param(u16 min, u16 max, u16 latency,
|
2011-02-11 22:28:55 +01:00
|
|
|
u16 to_multiplier)
|
|
|
|
{
|
|
|
|
u16 max_latency;
|
|
|
|
|
|
|
|
if (min > max || min < 6 || max > 3200)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (to_multiplier < 10 || to_multiplier > 3200)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (max >= to_multiplier * 8)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
max_latency = (to_multiplier * 8 / max) - 1;
|
|
|
|
if (latency > 499 || latency > max_latency)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
|
|
|
struct hci_conn *hcon = conn->hcon;
|
|
|
|
struct l2cap_conn_param_update_req *req;
|
|
|
|
struct l2cap_conn_param_update_rsp rsp;
|
|
|
|
u16 min, max, latency, to_multiplier, cmd_len;
|
2011-02-16 23:44:53 +01:00
|
|
|
int err;
|
2011-02-11 22:28:55 +01:00
|
|
|
|
|
|
|
if (!(hcon->link_mode & HCI_LM_MASTER))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
cmd_len = __le16_to_cpu(cmd->len);
|
|
|
|
if (cmd_len != sizeof(struct l2cap_conn_param_update_req))
|
|
|
|
return -EPROTO;
|
|
|
|
|
|
|
|
req = (struct l2cap_conn_param_update_req *) data;
|
2011-02-17 23:16:55 +01:00
|
|
|
min = __le16_to_cpu(req->min);
|
|
|
|
max = __le16_to_cpu(req->max);
|
2011-02-11 22:28:55 +01:00
|
|
|
latency = __le16_to_cpu(req->latency);
|
|
|
|
to_multiplier = __le16_to_cpu(req->to_multiplier);
|
|
|
|
|
|
|
|
BT_DBG("min 0x%4.4x max 0x%4.4x latency: 0x%4.4x Timeout: 0x%4.4x",
|
|
|
|
min, max, latency, to_multiplier);
|
|
|
|
|
|
|
|
memset(&rsp, 0, sizeof(rsp));
|
2011-02-16 23:44:53 +01:00
|
|
|
|
|
|
|
err = l2cap_check_conn_param(min, max, latency, to_multiplier);
|
|
|
|
if (err)
|
2011-02-11 22:28:55 +01:00
|
|
|
rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_REJECTED);
|
|
|
|
else
|
|
|
|
rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_ACCEPTED);
|
|
|
|
|
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_CONN_PARAM_UPDATE_RSP,
|
|
|
|
sizeof(rsp), &rsp);
|
|
|
|
|
2011-02-16 23:44:53 +01:00
|
|
|
if (!err)
|
|
|
|
hci_le_conn_update(hcon, min, max, latency, to_multiplier);
|
|
|
|
|
2011-02-11 22:28:55 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-02-11 22:28:54 +01:00
|
|
|
static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
switch (cmd->code) {
|
|
|
|
case L2CAP_COMMAND_REJ:
|
|
|
|
l2cap_command_rej(conn, cmd, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CONN_REQ:
|
|
|
|
err = l2cap_connect_req(conn, cmd, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CONN_RSP:
|
|
|
|
err = l2cap_connect_rsp(conn, cmd, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CONF_REQ:
|
|
|
|
err = l2cap_config_req(conn, cmd, cmd_len, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CONF_RSP:
|
|
|
|
err = l2cap_config_rsp(conn, cmd, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_DISCONN_REQ:
|
|
|
|
err = l2cap_disconnect_req(conn, cmd, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_DISCONN_RSP:
|
|
|
|
err = l2cap_disconnect_rsp(conn, cmd, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_ECHO_REQ:
|
|
|
|
l2cap_send_cmd(conn, cmd->ident, L2CAP_ECHO_RSP, cmd_len, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_ECHO_RSP:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_INFO_REQ:
|
|
|
|
err = l2cap_information_req(conn, cmd, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_INFO_RSP:
|
|
|
|
err = l2cap_information_rsp(conn, cmd, data);
|
|
|
|
break;
|
|
|
|
|
2011-11-03 00:18:32 +01:00
|
|
|
case L2CAP_CREATE_CHAN_REQ:
|
|
|
|
err = l2cap_create_channel_req(conn, cmd, cmd_len, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_CREATE_CHAN_RSP:
|
|
|
|
err = l2cap_create_channel_rsp(conn, cmd, data);
|
|
|
|
break;
|
|
|
|
|
2011-11-03 00:18:35 +01:00
|
|
|
case L2CAP_MOVE_CHAN_REQ:
|
|
|
|
err = l2cap_move_channel_req(conn, cmd, cmd_len, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MOVE_CHAN_RSP:
|
|
|
|
err = l2cap_move_channel_rsp(conn, cmd, cmd_len, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MOVE_CHAN_CFM:
|
|
|
|
err = l2cap_move_channel_confirm(conn, cmd, cmd_len, data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MOVE_CHAN_CFM_RSP:
|
|
|
|
err = l2cap_move_channel_confirm_rsp(conn, cmd, cmd_len, data);
|
|
|
|
break;
|
|
|
|
|
2011-02-11 22:28:54 +01:00
|
|
|
default:
|
|
|
|
BT_ERR("Unknown BR/EDR signaling command 0x%2.2x", cmd->code);
|
|
|
|
err = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
|
|
|
|
struct l2cap_cmd_hdr *cmd, u8 *data)
|
|
|
|
{
|
|
|
|
switch (cmd->code) {
|
|
|
|
case L2CAP_COMMAND_REJ:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case L2CAP_CONN_PARAM_UPDATE_REQ:
|
2011-02-11 22:28:55 +01:00
|
|
|
return l2cap_conn_param_update_req(conn, cmd, data);
|
2011-02-11 22:28:54 +01:00
|
|
|
|
|
|
|
case L2CAP_CONN_PARAM_UPDATE_RSP:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
default:
|
|
|
|
BT_ERR("Unknown LE signaling command 0x%2.2x", cmd->code);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void l2cap_sig_channel(struct l2cap_conn *conn,
|
|
|
|
struct sk_buff *skb)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
u8 *data = skb->data;
|
|
|
|
int len = skb->len;
|
|
|
|
struct l2cap_cmd_hdr cmd;
|
2011-02-11 22:28:54 +01:00
|
|
|
int err;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
l2cap_raw_recv(conn, skb);
|
|
|
|
|
|
|
|
while (len >= L2CAP_CMD_HDR_SIZE) {
|
2007-07-29 09:17:25 +02:00
|
|
|
u16 cmd_len;
|
2005-04-17 00:20:36 +02:00
|
|
|
memcpy(&cmd, data, L2CAP_CMD_HDR_SIZE);
|
|
|
|
data += L2CAP_CMD_HDR_SIZE;
|
|
|
|
len -= L2CAP_CMD_HDR_SIZE;
|
|
|
|
|
2007-07-29 09:17:25 +02:00
|
|
|
cmd_len = le16_to_cpu(cmd.len);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-07-29 09:17:25 +02:00
|
|
|
BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd.code, cmd_len, cmd.ident);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-07-29 09:17:25 +02:00
|
|
|
if (cmd_len > len || !cmd.ident) {
|
2005-04-17 00:20:36 +02:00
|
|
|
BT_DBG("corrupted command");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-02-11 22:28:54 +01:00
|
|
|
if (conn->hcon->type == LE_LINK)
|
|
|
|
err = l2cap_le_sig_cmd(conn, &cmd, data);
|
|
|
|
else
|
|
|
|
err = l2cap_bredr_sig_cmd(conn, &cmd, cmd_len, data);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (err) {
|
2011-07-10 07:47:44 +02:00
|
|
|
struct l2cap_cmd_rej_unk rej;
|
2011-03-23 18:38:32 +01:00
|
|
|
|
|
|
|
BT_ERR("Wrong link type (%d)", err);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* FIXME: Map err to a valid reason */
|
2011-07-10 07:47:44 +02:00
|
|
|
rej.reason = cpu_to_le16(L2CAP_REJ_NOT_UNDERSTOOD);
|
2005-04-17 00:20:36 +02:00
|
|
|
l2cap_send_cmd(conn, cmd.ident, L2CAP_COMMAND_REJ, sizeof(rej), &rej);
|
|
|
|
}
|
|
|
|
|
2007-07-29 09:17:25 +02:00
|
|
|
data += cmd_len;
|
|
|
|
len -= cmd_len;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
kfree_skb(skb);
|
|
|
|
}
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
static int l2cap_check_fcs(struct l2cap_chan *chan, struct sk_buff *skb)
|
2009-08-21 03:26:02 +02:00
|
|
|
{
|
|
|
|
u16 our_fcs, rcv_fcs;
|
2011-10-11 12:37:52 +02:00
|
|
|
int hdr_size;
|
|
|
|
|
|
|
|
if (test_bit(FLAG_EXT_CTRL, &chan->flags))
|
|
|
|
hdr_size = L2CAP_EXT_HDR_SIZE;
|
|
|
|
else
|
|
|
|
hdr_size = L2CAP_ENH_HDR_SIZE;
|
2009-08-21 03:26:02 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16) {
|
2011-10-17 11:19:58 +02:00
|
|
|
skb_trim(skb, skb->len - L2CAP_FCS_SIZE);
|
2009-08-21 03:26:02 +02:00
|
|
|
rcv_fcs = get_unaligned_le16(skb->data + skb->len);
|
|
|
|
our_fcs = crc16(0, skb->data - hdr_size, skb->len + hdr_size);
|
|
|
|
|
|
|
|
if (our_fcs != rcv_fcs)
|
2010-06-22 18:56:27 +02:00
|
|
|
return -EBADMSG;
|
2009-08-21 03:26:02 +02:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
static inline void l2cap_send_i_or_rr_or_rnr(struct l2cap_chan *chan)
|
2010-05-01 21:15:36 +02:00
|
|
|
{
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control = 0;
|
2010-05-01 21:15:36 +02:00
|
|
|
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->frames_sent = 0;
|
2010-05-01 21:15:36 +02:00
|
|
|
|
2011-10-11 12:37:46 +02:00
|
|
|
control |= __set_reqseq(chan, chan->buffer_seq);
|
2010-05-01 21:15:36 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) {
|
2011-10-11 12:37:44 +02:00
|
|
|
control |= __set_ctrl_super(chan, L2CAP_SUPER_RNR);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_sframe(chan, control);
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_RNR_SENT, &chan->conn_state);
|
2010-05-01 21:15:36 +02:00
|
|
|
}
|
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state))
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_retransmit_frames(chan);
|
2010-05-01 21:15:36 +02:00
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_ertm_send(chan);
|
2010-05-01 21:15:36 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (!test_bit(CONN_LOCAL_BUSY, &chan->conn_state) &&
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->frames_sent == 0) {
|
2011-10-11 12:37:44 +02:00
|
|
|
control |= __set_ctrl_super(chan, L2CAP_SUPER_RR);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_sframe(chan, control);
|
2010-05-01 21:15:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-11 12:37:47 +02:00
|
|
|
static int l2cap_add_to_srej_queue(struct l2cap_chan *chan, struct sk_buff *skb, u16 tx_seq, u8 sar)
|
2009-08-21 03:26:03 +02:00
|
|
|
{
|
|
|
|
struct sk_buff *next_skb;
|
2010-05-31 23:35:44 +02:00
|
|
|
int tx_seq_offset, next_tx_seq_offset;
|
2009-08-21 03:26:03 +02:00
|
|
|
|
|
|
|
bt_cb(skb)->tx_seq = tx_seq;
|
|
|
|
bt_cb(skb)->sar = sar;
|
|
|
|
|
2011-03-26 00:36:10 +01:00
|
|
|
next_skb = skb_peek(&chan->srej_q);
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-10-17 11:19:57 +02:00
|
|
|
tx_seq_offset = __seq_offset(chan, tx_seq, chan->buffer_seq);
|
2010-05-31 23:35:44 +02:00
|
|
|
|
2011-11-16 09:32:19 +01:00
|
|
|
while (next_skb) {
|
2010-05-01 21:15:44 +02:00
|
|
|
if (bt_cb(next_skb)->tx_seq == tx_seq)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2011-10-17 11:19:57 +02:00
|
|
|
next_tx_seq_offset = __seq_offset(chan,
|
|
|
|
bt_cb(next_skb)->tx_seq, chan->buffer_seq);
|
2010-05-31 23:35:44 +02:00
|
|
|
|
|
|
|
if (next_tx_seq_offset > tx_seq_offset) {
|
2011-03-26 00:36:10 +01:00
|
|
|
__skb_queue_before(&chan->srej_q, next_skb, skb);
|
2010-05-01 21:15:44 +02:00
|
|
|
return 0;
|
2009-08-21 03:26:03 +02:00
|
|
|
}
|
|
|
|
|
2011-03-26 00:36:10 +01:00
|
|
|
if (skb_queue_is_last(&chan->srej_q, next_skb))
|
2011-11-16 09:32:19 +01:00
|
|
|
next_skb = NULL;
|
|
|
|
else
|
|
|
|
next_skb = skb_queue_next(&chan->srej_q, next_skb);
|
|
|
|
}
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-03-26 00:36:10 +01:00
|
|
|
__skb_queue_tail(&chan->srej_q, skb);
|
2010-05-01 21:15:44 +02:00
|
|
|
|
|
|
|
return 0;
|
2009-08-21 03:26:03 +02:00
|
|
|
}
|
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
static void append_skb_frag(struct sk_buff *skb,
|
|
|
|
struct sk_buff *new_frag, struct sk_buff **last_frag)
|
2010-05-01 21:15:44 +02:00
|
|
|
{
|
2011-07-22 23:54:00 +02:00
|
|
|
/* skb->len reflects data in skb as well as all fragments
|
|
|
|
* skb->data_len reflects only data in fragments
|
|
|
|
*/
|
|
|
|
if (!skb_has_frag_list(skb))
|
|
|
|
skb_shinfo(skb)->frag_list = new_frag;
|
|
|
|
|
|
|
|
new_frag->next = NULL;
|
|
|
|
|
|
|
|
(*last_frag)->next = new_frag;
|
|
|
|
*last_frag = new_frag;
|
|
|
|
|
|
|
|
skb->len += new_frag->len;
|
|
|
|
skb->data_len += new_frag->len;
|
|
|
|
skb->truesize += new_frag->truesize;
|
|
|
|
}
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
static int l2cap_reassemble_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u32 control)
|
2011-07-22 23:54:00 +02:00
|
|
|
{
|
|
|
|
int err = -EINVAL;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-10-11 12:37:45 +02:00
|
|
|
switch (__get_ctrl_sar(chan, control)) {
|
|
|
|
case L2CAP_SAR_UNSEGMENTED:
|
2011-07-22 23:54:00 +02:00
|
|
|
if (chan->sdu)
|
|
|
|
break;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
err = chan->ops->recv(chan->data, skb);
|
|
|
|
break;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-10-11 12:37:45 +02:00
|
|
|
case L2CAP_SAR_START:
|
2011-07-22 23:54:00 +02:00
|
|
|
if (chan->sdu)
|
|
|
|
break;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-03-26 00:09:37 +01:00
|
|
|
chan->sdu_len = get_unaligned_le16(skb->data);
|
2011-10-17 11:19:58 +02:00
|
|
|
skb_pull(skb, L2CAP_SDULEN_SIZE);
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
if (chan->sdu_len > chan->imtu) {
|
|
|
|
err = -EMSGSIZE;
|
|
|
|
break;
|
|
|
|
}
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
if (skb->len >= chan->sdu_len)
|
|
|
|
break;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
chan->sdu = skb;
|
|
|
|
chan->sdu_last_frag = skb;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
skb = NULL;
|
|
|
|
err = 0;
|
2010-05-01 21:15:44 +02:00
|
|
|
break;
|
|
|
|
|
2011-10-11 12:37:45 +02:00
|
|
|
case L2CAP_SAR_CONTINUE:
|
2011-03-26 00:09:37 +01:00
|
|
|
if (!chan->sdu)
|
2011-07-22 23:54:00 +02:00
|
|
|
break;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
append_skb_frag(chan->sdu, skb,
|
|
|
|
&chan->sdu_last_frag);
|
|
|
|
skb = NULL;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
if (chan->sdu->len >= chan->sdu_len)
|
|
|
|
break;
|
2010-05-01 21:15:45 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
err = 0;
|
2010-05-01 21:15:44 +02:00
|
|
|
break;
|
|
|
|
|
2011-10-11 12:37:45 +02:00
|
|
|
case L2CAP_SAR_END:
|
2011-03-26 00:09:37 +01:00
|
|
|
if (!chan->sdu)
|
2011-07-22 23:54:00 +02:00
|
|
|
break;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
append_skb_frag(chan->sdu, skb,
|
|
|
|
&chan->sdu_last_frag);
|
|
|
|
skb = NULL;
|
2010-05-01 21:15:45 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
if (chan->sdu->len != chan->sdu_len)
|
|
|
|
break;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
err = chan->ops->recv(chan->data, chan->sdu);
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
if (!err) {
|
|
|
|
/* Reassembly complete */
|
|
|
|
chan->sdu = NULL;
|
|
|
|
chan->sdu_last_frag = NULL;
|
|
|
|
chan->sdu_len = 0;
|
2010-05-01 21:15:44 +02:00
|
|
|
}
|
2010-05-01 21:15:44 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
if (err) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
kfree_skb(chan->sdu);
|
|
|
|
chan->sdu = NULL;
|
|
|
|
chan->sdu_last_frag = NULL;
|
|
|
|
chan->sdu_len = 0;
|
|
|
|
}
|
2010-05-01 21:15:44 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
return err;
|
2010-05-01 21:15:44 +02:00
|
|
|
}
|
|
|
|
|
2011-07-07 18:39:01 +02:00
|
|
|
static void l2cap_ertm_enter_local_busy(struct l2cap_chan *chan)
|
2010-06-22 00:39:50 +02:00
|
|
|
{
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2010-06-22 00:39:50 +02:00
|
|
|
|
2011-07-07 18:39:01 +02:00
|
|
|
BT_DBG("chan %p, Enter local busy", chan);
|
2010-06-22 00:39:50 +02:00
|
|
|
|
2011-07-07 18:39:01 +02:00
|
|
|
set_bit(CONN_LOCAL_BUSY, &chan->conn_state);
|
|
|
|
|
2011-10-11 12:37:46 +02:00
|
|
|
control = __set_reqseq(chan, chan->buffer_seq);
|
2011-10-11 12:37:44 +02:00
|
|
|
control |= __set_ctrl_super(chan, L2CAP_SUPER_RNR);
|
2011-07-07 18:39:01 +02:00
|
|
|
l2cap_send_sframe(chan, control);
|
|
|
|
|
|
|
|
set_bit(CONN_RNR_SENT, &chan->conn_state);
|
|
|
|
|
|
|
|
__clear_ack_timer(chan);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_ertm_exit_local_busy(struct l2cap_chan *chan)
|
|
|
|
{
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2010-06-22 00:39:50 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (!test_bit(CONN_RNR_SENT, &chan->conn_state))
|
2010-06-22 00:39:50 +02:00
|
|
|
goto done;
|
|
|
|
|
2011-10-11 12:37:46 +02:00
|
|
|
control = __set_reqseq(chan, chan->buffer_seq);
|
2011-10-11 12:37:50 +02:00
|
|
|
control |= __set_ctrl_poll(chan);
|
2011-10-11 12:37:44 +02:00
|
|
|
control |= __set_ctrl_super(chan, L2CAP_SUPER_RR);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_sframe(chan, control);
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->retry_count = 1;
|
2010-06-22 00:39:50 +02:00
|
|
|
|
2011-05-17 20:13:19 +02:00
|
|
|
__clear_retrans_timer(chan);
|
|
|
|
__set_monitor_timer(chan);
|
2010-06-22 00:39:50 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_WAIT_F, &chan->conn_state);
|
2010-06-22 00:39:50 +02:00
|
|
|
|
|
|
|
done:
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_LOCAL_BUSY, &chan->conn_state);
|
|
|
|
clear_bit(CONN_RNR_SENT, &chan->conn_state);
|
2010-06-22 00:39:50 +02:00
|
|
|
|
2011-04-04 20:59:54 +02:00
|
|
|
BT_DBG("chan %p, Exit local busy", chan);
|
2010-06-22 00:39:50 +02:00
|
|
|
}
|
|
|
|
|
2011-07-07 18:39:02 +02:00
|
|
|
void l2cap_chan_busy(struct l2cap_chan *chan, int busy)
|
2010-05-01 21:15:44 +02:00
|
|
|
{
|
2011-07-07 18:39:02 +02:00
|
|
|
if (chan->mode == L2CAP_MODE_ERTM) {
|
|
|
|
if (busy)
|
|
|
|
l2cap_ertm_enter_local_busy(chan);
|
|
|
|
else
|
|
|
|
l2cap_ertm_exit_local_busy(chan);
|
2010-05-01 21:15:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-11 12:37:47 +02:00
|
|
|
static void l2cap_check_srej_gap(struct l2cap_chan *chan, u16 tx_seq)
|
2009-08-21 03:26:03 +02:00
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-07-07 18:39:02 +02:00
|
|
|
while ((skb = skb_peek(&chan->srej_q)) &&
|
|
|
|
!test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) {
|
|
|
|
int err;
|
|
|
|
|
2009-08-21 03:26:03 +02:00
|
|
|
if (bt_cb(skb)->tx_seq != tx_seq)
|
|
|
|
break;
|
|
|
|
|
2011-03-26 00:36:10 +01:00
|
|
|
skb = skb_dequeue(&chan->srej_q);
|
2011-10-11 12:37:45 +02:00
|
|
|
control = __set_ctrl_sar(chan, bt_cb(skb)->sar);
|
2011-07-22 23:54:00 +02:00
|
|
|
err = l2cap_reassemble_sdu(chan, skb, control);
|
2011-07-07 18:39:02 +02:00
|
|
|
|
|
|
|
if (err < 0) {
|
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-10-17 11:19:57 +02:00
|
|
|
chan->buffer_seq_srej = __next_seq(chan, chan->buffer_seq_srej);
|
|
|
|
tx_seq = __next_seq(chan, tx_seq);
|
2009-08-21 03:26:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-11 12:37:47 +02:00
|
|
|
static void l2cap_resend_srejframe(struct l2cap_chan *chan, u16 tx_seq)
|
2009-08-21 03:26:03 +02:00
|
|
|
{
|
|
|
|
struct srej_list *l, *tmp;
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-04-04 20:40:12 +02:00
|
|
|
list_for_each_entry_safe(l, tmp, &chan->srej_l, list) {
|
2009-08-21 03:26:03 +02:00
|
|
|
if (l->tx_seq == tx_seq) {
|
|
|
|
list_del(&l->list);
|
|
|
|
kfree(l);
|
|
|
|
return;
|
|
|
|
}
|
2011-10-11 12:37:44 +02:00
|
|
|
control = __set_ctrl_super(chan, L2CAP_SUPER_SREJ);
|
2011-10-11 12:37:46 +02:00
|
|
|
control |= __set_reqseq(chan, l->tx_seq);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_sframe(chan, control);
|
2009-08-21 03:26:03 +02:00
|
|
|
list_del(&l->list);
|
2011-04-04 20:40:12 +02:00
|
|
|
list_add_tail(&l->list, &chan->srej_l);
|
2009-08-21 03:26:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-16 09:32:18 +01:00
|
|
|
static int l2cap_send_srejframe(struct l2cap_chan *chan, u16 tx_seq)
|
2009-08-21 03:26:03 +02:00
|
|
|
{
|
|
|
|
struct srej_list *new;
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-03-25 23:58:34 +01:00
|
|
|
while (tx_seq != chan->expected_tx_seq) {
|
2011-10-11 12:37:44 +02:00
|
|
|
control = __set_ctrl_super(chan, L2CAP_SUPER_SREJ);
|
2011-10-11 12:37:46 +02:00
|
|
|
control |= __set_reqseq(chan, chan->expected_tx_seq);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_sframe(chan, control);
|
2009-08-21 03:26:03 +02:00
|
|
|
|
|
|
|
new = kzalloc(sizeof(struct srej_list), GFP_ATOMIC);
|
2011-11-16 09:32:18 +01:00
|
|
|
if (!new)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-03-25 23:58:34 +01:00
|
|
|
new->tx_seq = chan->expected_tx_seq;
|
2011-10-17 11:19:57 +02:00
|
|
|
|
|
|
|
chan->expected_tx_seq = __next_seq(chan, chan->expected_tx_seq);
|
|
|
|
|
2011-04-04 20:40:12 +02:00
|
|
|
list_add_tail(&new->list, &chan->srej_l);
|
2009-08-21 03:26:03 +02:00
|
|
|
}
|
2011-10-17 11:19:57 +02:00
|
|
|
|
|
|
|
chan->expected_tx_seq = __next_seq(chan, chan->expected_tx_seq);
|
2011-11-16 09:32:18 +01:00
|
|
|
|
|
|
|
return 0;
|
2009-08-21 03:26:03 +02:00
|
|
|
}
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
static inline int l2cap_data_channel_iframe(struct l2cap_chan *chan, u32 rx_control, struct sk_buff *skb)
|
2009-08-21 03:25:57 +02:00
|
|
|
{
|
2011-10-11 12:37:47 +02:00
|
|
|
u16 tx_seq = __get_txseq(chan, rx_control);
|
2011-10-11 12:37:46 +02:00
|
|
|
u16 req_seq = __get_reqseq(chan, rx_control);
|
2011-10-11 12:37:45 +02:00
|
|
|
u8 sar = __get_ctrl_sar(chan, rx_control);
|
2010-05-10 23:32:04 +02:00
|
|
|
int tx_seq_offset, expected_tx_seq_offset;
|
2011-04-13 20:57:03 +02:00
|
|
|
int num_to_ack = (chan->tx_win/6) + 1;
|
2009-08-21 03:25:57 +02:00
|
|
|
int err = 0;
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
BT_DBG("chan %p len %d tx_seq %d rx_control 0x%8.8x", chan, skb->len,
|
2011-03-25 23:43:39 +01:00
|
|
|
tx_seq, rx_control);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-10-11 12:37:49 +02:00
|
|
|
if (__is_ctrl_final(chan, rx_control) &&
|
2011-06-11 02:28:49 +02:00
|
|
|
test_bit(CONN_WAIT_F, &chan->conn_state)) {
|
2011-05-17 20:13:19 +02:00
|
|
|
__clear_monitor_timer(chan);
|
2011-04-01 05:38:50 +02:00
|
|
|
if (chan->unacked_frames > 0)
|
2011-05-17 20:13:19 +02:00
|
|
|
__set_retrans_timer(chan);
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_WAIT_F, &chan->conn_state);
|
2010-05-01 21:15:37 +02:00
|
|
|
}
|
|
|
|
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->expected_ack_seq = req_seq;
|
|
|
|
l2cap_drop_acked_frames(chan);
|
2009-10-03 07:34:38 +02:00
|
|
|
|
2011-10-17 11:19:57 +02:00
|
|
|
tx_seq_offset = __seq_offset(chan, tx_seq, chan->buffer_seq);
|
2010-05-01 21:15:44 +02:00
|
|
|
|
|
|
|
/* invalid tx_seq */
|
2011-04-13 20:57:03 +02:00
|
|
|
if (tx_seq_offset >= chan->tx_win) {
|
2011-04-14 01:23:55 +02:00
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
|
2010-05-01 21:15:44 +02:00
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state))
|
2010-05-01 21:15:44 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-06-29 23:35:19 +02:00
|
|
|
if (tx_seq == chan->expected_tx_seq)
|
|
|
|
goto expected;
|
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_SREJ_SENT, &chan->conn_state)) {
|
2009-08-21 03:26:03 +02:00
|
|
|
struct srej_list *first;
|
2009-08-21 03:25:59 +02:00
|
|
|
|
2011-04-04 20:40:12 +02:00
|
|
|
first = list_first_entry(&chan->srej_l,
|
2009-08-21 03:26:03 +02:00
|
|
|
struct srej_list, list);
|
|
|
|
if (tx_seq == first->tx_seq) {
|
2011-03-25 23:58:34 +01:00
|
|
|
l2cap_add_to_srej_queue(chan, skb, tx_seq, sar);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_check_srej_gap(chan, tx_seq);
|
2009-08-21 03:26:03 +02:00
|
|
|
|
|
|
|
list_del(&first->list);
|
|
|
|
kfree(first);
|
|
|
|
|
2011-04-04 20:40:12 +02:00
|
|
|
if (list_empty(&chan->srej_l)) {
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->buffer_seq = chan->buffer_seq_srej;
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_SREJ_SENT, &chan->conn_state);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_ack(chan);
|
2011-04-04 20:59:54 +02:00
|
|
|
BT_DBG("chan %p, Exit SREJ_SENT", chan);
|
2009-08-21 03:26:03 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
struct srej_list *l;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
|
|
|
/* duplicated tx_seq */
|
2011-03-25 23:58:34 +01:00
|
|
|
if (l2cap_add_to_srej_queue(chan, skb, tx_seq, sar) < 0)
|
2010-05-01 21:15:44 +02:00
|
|
|
goto drop;
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-04-04 20:40:12 +02:00
|
|
|
list_for_each_entry(l, &chan->srej_l, list) {
|
2009-08-21 03:26:03 +02:00
|
|
|
if (l->tx_seq == tx_seq) {
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_resend_srejframe(chan, tx_seq);
|
2009-08-21 03:26:03 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
2011-11-16 09:32:18 +01:00
|
|
|
|
|
|
|
err = l2cap_send_srejframe(chan, tx_seq);
|
|
|
|
if (err < 0) {
|
|
|
|
l2cap_send_disconn_req(chan->conn, chan, -err);
|
|
|
|
return err;
|
|
|
|
}
|
2009-08-21 03:25:59 +02:00
|
|
|
}
|
|
|
|
} else {
|
2011-10-17 11:19:57 +02:00
|
|
|
expected_tx_seq_offset = __seq_offset(chan,
|
|
|
|
chan->expected_tx_seq, chan->buffer_seq);
|
2010-05-01 21:15:44 +02:00
|
|
|
|
|
|
|
/* duplicated tx_seq */
|
|
|
|
if (tx_seq_offset < expected_tx_seq_offset)
|
|
|
|
goto drop;
|
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_SREJ_SENT, &chan->conn_state);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-04-04 20:59:54 +02:00
|
|
|
BT_DBG("chan %p, Enter SREJ", chan);
|
2010-04-19 19:45:38 +02:00
|
|
|
|
2011-04-04 20:40:12 +02:00
|
|
|
INIT_LIST_HEAD(&chan->srej_l);
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->buffer_seq_srej = chan->buffer_seq;
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-03-26 00:36:10 +01:00
|
|
|
__skb_queue_head_init(&chan->srej_q);
|
2011-03-25 23:58:34 +01:00
|
|
|
l2cap_add_to_srej_queue(chan, skb, tx_seq, sar);
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_SEND_PBIT, &chan->conn_state);
|
2009-08-21 03:26:04 +02:00
|
|
|
|
2011-11-16 09:32:18 +01:00
|
|
|
err = l2cap_send_srejframe(chan, tx_seq);
|
|
|
|
if (err < 0) {
|
|
|
|
l2cap_send_disconn_req(chan->conn, chan, -err);
|
|
|
|
return err;
|
|
|
|
}
|
2010-05-12 23:32:04 +02:00
|
|
|
|
2011-05-17 20:13:19 +02:00
|
|
|
__clear_ack_timer(chan);
|
2009-08-21 03:25:57 +02:00
|
|
|
}
|
2009-08-21 03:25:59 +02:00
|
|
|
return 0;
|
|
|
|
|
2009-08-21 03:26:03 +02:00
|
|
|
expected:
|
2011-10-17 11:19:57 +02:00
|
|
|
chan->expected_tx_seq = __next_seq(chan, chan->expected_tx_seq);
|
2009-08-21 03:26:03 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_SREJ_SENT, &chan->conn_state)) {
|
2010-05-01 21:15:42 +02:00
|
|
|
bt_cb(skb)->tx_seq = tx_seq;
|
|
|
|
bt_cb(skb)->sar = sar;
|
2011-03-26 00:36:10 +01:00
|
|
|
__skb_queue_tail(&chan->srej_q, skb);
|
2009-08-21 03:26:03 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
err = l2cap_reassemble_sdu(chan, skb, rx_control);
|
2011-10-17 11:19:57 +02:00
|
|
|
chan->buffer_seq = __next_seq(chan, chan->buffer_seq);
|
|
|
|
|
2011-07-07 18:39:02 +02:00
|
|
|
if (err < 0) {
|
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
|
|
|
|
return err;
|
|
|
|
}
|
2010-06-16 22:21:44 +02:00
|
|
|
|
2011-10-11 12:37:49 +02:00
|
|
|
if (__is_ctrl_final(chan, rx_control)) {
|
2011-06-11 02:28:49 +02:00
|
|
|
if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state))
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_retransmit_frames(chan);
|
2009-10-03 07:34:39 +02:00
|
|
|
}
|
|
|
|
|
2010-05-01 21:15:39 +02:00
|
|
|
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->num_acked = (chan->num_acked + 1) % num_to_ack;
|
|
|
|
if (chan->num_acked == num_to_ack - 1)
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_ack(chan);
|
2011-06-24 00:30:48 +02:00
|
|
|
else
|
|
|
|
__set_ack_timer(chan);
|
2010-05-01 21:15:37 +02:00
|
|
|
|
2009-08-21 03:26:03 +02:00
|
|
|
return 0;
|
2010-05-01 21:15:44 +02:00
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return 0;
|
2009-08-21 03:25:57 +02:00
|
|
|
}
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
static inline void l2cap_data_channel_rrframe(struct l2cap_chan *chan, u32 rx_control)
|
2009-08-21 03:25:57 +02:00
|
|
|
{
|
2011-10-17 11:19:56 +02:00
|
|
|
BT_DBG("chan %p, req_seq %d ctrl 0x%8.8x", chan,
|
2011-10-11 12:37:46 +02:00
|
|
|
__get_reqseq(chan, rx_control), rx_control);
|
2010-04-19 19:45:38 +02:00
|
|
|
|
2011-10-11 12:37:46 +02:00
|
|
|
chan->expected_ack_seq = __get_reqseq(chan, rx_control);
|
2011-03-25 23:58:34 +01:00
|
|
|
l2cap_drop_acked_frames(chan);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-10-11 12:37:50 +02:00
|
|
|
if (__is_ctrl_poll(chan, rx_control)) {
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_SEND_FBIT, &chan->conn_state);
|
|
|
|
if (test_bit(CONN_SREJ_SENT, &chan->conn_state)) {
|
|
|
|
if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state) &&
|
2011-04-01 05:38:50 +02:00
|
|
|
(chan->unacked_frames > 0))
|
2011-05-17 20:13:19 +02:00
|
|
|
__set_retrans_timer(chan);
|
2010-05-01 21:15:39 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_REMOTE_BUSY, &chan->conn_state);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_srejtail(chan);
|
2010-05-01 21:15:39 +02:00
|
|
|
} else {
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_i_or_rr_or_rnr(chan);
|
2010-05-01 21:15:39 +02:00
|
|
|
}
|
2010-05-01 21:15:37 +02:00
|
|
|
|
2011-10-11 12:37:49 +02:00
|
|
|
} else if (__is_ctrl_final(chan, rx_control)) {
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_REMOTE_BUSY, &chan->conn_state);
|
2009-10-03 07:34:39 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state))
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_retransmit_frames(chan);
|
2009-08-26 09:04:02 +02:00
|
|
|
|
2010-05-01 21:15:38 +02:00
|
|
|
} else {
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state) &&
|
2011-04-01 05:38:50 +02:00
|
|
|
(chan->unacked_frames > 0))
|
2011-05-17 20:13:19 +02:00
|
|
|
__set_retrans_timer(chan);
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_REMOTE_BUSY, &chan->conn_state);
|
|
|
|
if (test_bit(CONN_SREJ_SENT, &chan->conn_state))
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_ack(chan);
|
2010-12-01 15:58:24 +01:00
|
|
|
else
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_ertm_send(chan);
|
2010-05-01 21:15:38 +02:00
|
|
|
}
|
|
|
|
}
|
2009-08-26 09:04:02 +02:00
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
static inline void l2cap_data_channel_rejframe(struct l2cap_chan *chan, u32 rx_control)
|
2010-05-01 21:15:38 +02:00
|
|
|
{
|
2011-10-11 12:37:46 +02:00
|
|
|
u16 tx_seq = __get_reqseq(chan, rx_control);
|
2009-08-21 03:25:59 +02:00
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
BT_DBG("chan %p, req_seq %d ctrl 0x%8.8x", chan, tx_seq, rx_control);
|
2010-04-19 19:45:38 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_REMOTE_BUSY, &chan->conn_state);
|
2010-05-01 21:15:38 +02:00
|
|
|
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->expected_ack_seq = tx_seq;
|
|
|
|
l2cap_drop_acked_frames(chan);
|
2010-05-01 21:15:38 +02:00
|
|
|
|
2011-10-11 12:37:49 +02:00
|
|
|
if (__is_ctrl_final(chan, rx_control)) {
|
2011-06-11 02:28:49 +02:00
|
|
|
if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state))
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_retransmit_frames(chan);
|
2010-05-01 21:15:38 +02:00
|
|
|
} else {
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_retransmit_frames(chan);
|
2009-08-21 03:25:59 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_WAIT_F, &chan->conn_state))
|
|
|
|
set_bit(CONN_REJ_ACT, &chan->conn_state);
|
2010-05-01 21:15:38 +02:00
|
|
|
}
|
|
|
|
}
|
2011-10-17 11:19:56 +02:00
|
|
|
static inline void l2cap_data_channel_srejframe(struct l2cap_chan *chan, u32 rx_control)
|
2010-05-01 21:15:38 +02:00
|
|
|
{
|
2011-10-11 12:37:46 +02:00
|
|
|
u16 tx_seq = __get_reqseq(chan, rx_control);
|
2009-08-21 03:25:59 +02:00
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
BT_DBG("chan %p, req_seq %d ctrl 0x%8.8x", chan, tx_seq, rx_control);
|
2010-04-19 19:45:38 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_REMOTE_BUSY, &chan->conn_state);
|
2009-08-26 09:04:02 +02:00
|
|
|
|
2011-10-11 12:37:50 +02:00
|
|
|
if (__is_ctrl_poll(chan, rx_control)) {
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->expected_ack_seq = tx_seq;
|
|
|
|
l2cap_drop_acked_frames(chan);
|
2010-05-29 07:24:35 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_SEND_FBIT, &chan->conn_state);
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_retransmit_one_frame(chan, tx_seq);
|
2010-05-01 21:15:45 +02:00
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_ertm_send(chan);
|
2010-05-01 21:15:45 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_WAIT_F, &chan->conn_state)) {
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->srej_save_reqseq = tx_seq;
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_SREJ_ACT, &chan->conn_state);
|
2009-08-21 03:26:04 +02:00
|
|
|
}
|
2011-10-11 12:37:49 +02:00
|
|
|
} else if (__is_ctrl_final(chan, rx_control)) {
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_SREJ_ACT, &chan->conn_state) &&
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->srej_save_reqseq == tx_seq)
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_SREJ_ACT, &chan->conn_state);
|
2010-05-01 21:15:38 +02:00
|
|
|
else
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_retransmit_one_frame(chan, tx_seq);
|
2010-05-01 21:15:38 +02:00
|
|
|
} else {
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_retransmit_one_frame(chan, tx_seq);
|
2011-06-11 02:28:49 +02:00
|
|
|
if (test_bit(CONN_WAIT_F, &chan->conn_state)) {
|
2011-04-01 05:38:50 +02:00
|
|
|
chan->srej_save_reqseq = tx_seq;
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_SREJ_ACT, &chan->conn_state);
|
2009-08-21 03:26:04 +02:00
|
|
|
}
|
2010-05-01 21:15:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
static inline void l2cap_data_channel_rnrframe(struct l2cap_chan *chan, u32 rx_control)
|
2010-05-01 21:15:38 +02:00
|
|
|
{
|
2011-10-11 12:37:46 +02:00
|
|
|
u16 tx_seq = __get_reqseq(chan, rx_control);
|
2010-05-01 21:15:38 +02:00
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
BT_DBG("chan %p, req_seq %d ctrl 0x%8.8x", chan, tx_seq, rx_control);
|
2010-04-19 19:45:38 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_REMOTE_BUSY, &chan->conn_state);
|
2011-03-25 23:58:34 +01:00
|
|
|
chan->expected_ack_seq = tx_seq;
|
|
|
|
l2cap_drop_acked_frames(chan);
|
2010-05-01 21:15:38 +02:00
|
|
|
|
2011-10-11 12:37:50 +02:00
|
|
|
if (__is_ctrl_poll(chan, rx_control))
|
2011-06-11 02:28:49 +02:00
|
|
|
set_bit(CONN_SEND_FBIT, &chan->conn_state);
|
2010-05-29 07:24:35 +02:00
|
|
|
|
2011-06-11 02:28:49 +02:00
|
|
|
if (!test_bit(CONN_SREJ_SENT, &chan->conn_state)) {
|
2011-05-17 20:13:19 +02:00
|
|
|
__clear_retrans_timer(chan);
|
2011-10-11 12:37:50 +02:00
|
|
|
if (__is_ctrl_poll(chan, rx_control))
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_rr_or_rnr(chan, L2CAP_CTRL_FINAL);
|
2010-05-01 21:15:38 +02:00
|
|
|
return;
|
2010-05-01 21:15:38 +02:00
|
|
|
}
|
2010-05-01 21:15:38 +02:00
|
|
|
|
2011-10-11 12:37:50 +02:00
|
|
|
if (__is_ctrl_poll(chan, rx_control)) {
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_send_srejtail(chan);
|
2011-10-11 12:37:44 +02:00
|
|
|
} else {
|
|
|
|
rx_control = __set_ctrl_super(chan, L2CAP_SUPER_RR);
|
|
|
|
l2cap_send_sframe(chan, rx_control);
|
|
|
|
}
|
2010-05-01 21:15:38 +02:00
|
|
|
}
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
static inline int l2cap_data_channel_sframe(struct l2cap_chan *chan, u32 rx_control, struct sk_buff *skb)
|
2010-05-01 21:15:38 +02:00
|
|
|
{
|
2011-10-17 11:19:56 +02:00
|
|
|
BT_DBG("chan %p rx_control 0x%8.8x len %d", chan, rx_control, skb->len);
|
2010-05-01 21:15:38 +02:00
|
|
|
|
2011-10-11 12:37:49 +02:00
|
|
|
if (__is_ctrl_final(chan, rx_control) &&
|
2011-06-11 02:28:49 +02:00
|
|
|
test_bit(CONN_WAIT_F, &chan->conn_state)) {
|
2011-05-17 20:13:19 +02:00
|
|
|
__clear_monitor_timer(chan);
|
2011-04-01 05:38:50 +02:00
|
|
|
if (chan->unacked_frames > 0)
|
2011-05-17 20:13:19 +02:00
|
|
|
__set_retrans_timer(chan);
|
2011-06-11 02:28:49 +02:00
|
|
|
clear_bit(CONN_WAIT_F, &chan->conn_state);
|
2010-05-01 21:15:38 +02:00
|
|
|
}
|
|
|
|
|
2011-10-11 12:37:44 +02:00
|
|
|
switch (__get_ctrl_super(chan, rx_control)) {
|
|
|
|
case L2CAP_SUPER_RR:
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_data_channel_rrframe(chan, rx_control);
|
2009-08-21 03:26:03 +02:00
|
|
|
break;
|
|
|
|
|
2011-10-11 12:37:44 +02:00
|
|
|
case L2CAP_SUPER_REJ:
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_data_channel_rejframe(chan, rx_control);
|
2010-05-01 21:15:38 +02:00
|
|
|
break;
|
2009-08-26 09:04:02 +02:00
|
|
|
|
2011-10-11 12:37:44 +02:00
|
|
|
case L2CAP_SUPER_SREJ:
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_data_channel_srejframe(chan, rx_control);
|
2010-05-01 21:15:38 +02:00
|
|
|
break;
|
|
|
|
|
2011-10-11 12:37:44 +02:00
|
|
|
case L2CAP_SUPER_RNR:
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_data_channel_rnrframe(chan, rx_control);
|
2009-08-21 03:25:57 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-05-01 21:15:35 +02:00
|
|
|
kfree_skb(skb);
|
2009-08-21 03:25:57 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-06-21 23:53:22 +02:00
|
|
|
static int l2cap_ertm_data_rcv(struct sock *sk, struct sk_buff *skb)
|
|
|
|
{
|
2011-03-25 23:43:39 +01:00
|
|
|
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2011-10-11 12:37:46 +02:00
|
|
|
u16 req_seq;
|
2010-06-21 23:53:22 +02:00
|
|
|
int len, next_tx_seq_offset, req_seq_offset;
|
|
|
|
|
2011-10-17 11:19:56 +02:00
|
|
|
control = __get_control(chan, skb->data);
|
|
|
|
skb_pull(skb, __ctrl_size(chan));
|
2010-06-21 23:53:22 +02:00
|
|
|
len = skb->len;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We can just drop the corrupted I-frame here.
|
|
|
|
* Receiver will miss it and start proper recovery
|
|
|
|
* procedures and ask retransmission.
|
|
|
|
*/
|
2011-04-13 20:57:03 +02:00
|
|
|
if (l2cap_check_fcs(chan, skb))
|
2010-06-21 23:53:22 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-10-11 12:37:48 +02:00
|
|
|
if (__is_sar_start(chan, control) && !__is_sframe(chan, control))
|
2011-10-17 11:19:58 +02:00
|
|
|
len -= L2CAP_SDULEN_SIZE;
|
2010-06-21 23:53:22 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16)
|
2011-10-17 11:19:58 +02:00
|
|
|
len -= L2CAP_FCS_SIZE;
|
2010-06-21 23:53:22 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (len > chan->mps) {
|
2011-04-14 01:23:55 +02:00
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
|
2010-06-21 23:53:22 +02:00
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2011-10-11 12:37:46 +02:00
|
|
|
req_seq = __get_reqseq(chan, control);
|
2010-06-21 23:53:22 +02:00
|
|
|
|
2011-10-17 11:19:57 +02:00
|
|
|
req_seq_offset = __seq_offset(chan, req_seq, chan->expected_ack_seq);
|
|
|
|
|
|
|
|
next_tx_seq_offset = __seq_offset(chan, chan->next_tx_seq,
|
|
|
|
chan->expected_ack_seq);
|
2010-06-21 23:53:22 +02:00
|
|
|
|
|
|
|
/* check for invalid req-seq */
|
|
|
|
if (req_seq_offset > next_tx_seq_offset) {
|
2011-04-14 01:23:55 +02:00
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
|
2010-06-21 23:53:22 +02:00
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2011-10-11 12:37:48 +02:00
|
|
|
if (!__is_sframe(chan, control)) {
|
2010-06-21 23:53:22 +02:00
|
|
|
if (len < 0) {
|
2011-04-14 01:23:55 +02:00
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
|
2010-06-21 23:53:22 +02:00
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_data_channel_iframe(chan, control, skb);
|
2010-06-21 23:53:22 +02:00
|
|
|
} else {
|
|
|
|
if (len != 0) {
|
|
|
|
BT_ERR("%d", len);
|
2011-04-14 01:23:55 +02:00
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
|
2010-06-21 23:53:22 +02:00
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2011-03-25 23:43:39 +01:00
|
|
|
l2cap_data_channel_sframe(chan, control, skb);
|
2010-06-21 23:53:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
static inline int l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk_buff *skb)
|
|
|
|
{
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2011-04-25 22:03:02 +02:00
|
|
|
struct sock *sk = NULL;
|
2011-10-17 11:19:56 +02:00
|
|
|
u32 control;
|
2011-10-11 12:37:47 +02:00
|
|
|
u16 tx_seq;
|
2010-06-21 23:53:22 +02:00
|
|
|
int len;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
chan = l2cap_get_chan_by_scid(conn, cid);
|
2011-03-25 04:22:30 +01:00
|
|
|
if (!chan) {
|
2005-04-17 00:20:36 +02:00
|
|
|
BT_DBG("unknown cid 0x%4.4x", cid);
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
sk = chan->sk;
|
2009-08-21 03:26:01 +02:00
|
|
|
|
2011-04-04 20:59:54 +02:00
|
|
|
BT_DBG("chan %p, len %d", chan, skb->len);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (chan->state != BT_CONNECTED)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
switch (chan->mode) {
|
2009-08-21 03:25:57 +02:00
|
|
|
case L2CAP_MODE_BASIC:
|
|
|
|
/* If socket recv buffers overflows we drop data here
|
|
|
|
* which is *bad* because L2CAP has to be reliable.
|
|
|
|
* But we don't have any other choice. L2CAP doesn't
|
|
|
|
* provide flow control mechanism. */
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->imtu < skb->len)
|
2009-08-21 03:25:57 +02:00
|
|
|
goto drop;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-05-16 22:57:22 +02:00
|
|
|
if (!chan->ops->recv(chan->data, skb))
|
2009-08-21 03:25:57 +02:00
|
|
|
goto done;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case L2CAP_MODE_ERTM:
|
2010-06-21 23:53:22 +02:00
|
|
|
if (!sock_owned_by_user(sk)) {
|
|
|
|
l2cap_ertm_data_rcv(sk, skb);
|
2010-05-01 21:15:37 +02:00
|
|
|
} else {
|
2010-06-21 23:53:22 +02:00
|
|
|
if (sk_add_backlog(sk, skb))
|
2010-05-01 21:15:37 +02:00
|
|
|
goto drop;
|
|
|
|
}
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2009-12-22 14:58:08 +01:00
|
|
|
goto done;
|
2009-08-21 03:25:57 +02:00
|
|
|
|
2009-08-21 03:26:01 +02:00
|
|
|
case L2CAP_MODE_STREAMING:
|
2011-10-17 11:19:56 +02:00
|
|
|
control = __get_control(chan, skb->data);
|
|
|
|
skb_pull(skb, __ctrl_size(chan));
|
2009-08-21 03:26:01 +02:00
|
|
|
len = skb->len;
|
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (l2cap_check_fcs(chan, skb))
|
2010-05-12 03:02:00 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-10-11 12:37:45 +02:00
|
|
|
if (__is_sar_start(chan, control))
|
2011-10-17 11:19:58 +02:00
|
|
|
len -= L2CAP_SDULEN_SIZE;
|
2009-08-21 03:26:01 +02:00
|
|
|
|
2011-04-13 20:57:03 +02:00
|
|
|
if (chan->fcs == L2CAP_FCS_CRC16)
|
2011-10-17 11:19:58 +02:00
|
|
|
len -= L2CAP_FCS_SIZE;
|
2009-08-21 03:26:02 +02:00
|
|
|
|
2011-10-11 12:37:48 +02:00
|
|
|
if (len > chan->mps || len < 0 || __is_sframe(chan, control))
|
2009-08-21 03:26:01 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-10-11 12:37:47 +02:00
|
|
|
tx_seq = __get_txseq(chan, control);
|
2009-08-21 03:26:01 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
if (chan->expected_tx_seq != tx_seq) {
|
|
|
|
/* Frame(s) missing - must discard partial SDU */
|
|
|
|
kfree_skb(chan->sdu);
|
|
|
|
chan->sdu = NULL;
|
|
|
|
chan->sdu_last_frag = NULL;
|
|
|
|
chan->sdu_len = 0;
|
2009-08-21 03:26:01 +02:00
|
|
|
|
2011-07-22 23:54:00 +02:00
|
|
|
/* TODO: Notify userland of missing data */
|
|
|
|
}
|
|
|
|
|
2011-10-17 11:19:57 +02:00
|
|
|
chan->expected_tx_seq = __next_seq(chan, tx_seq);
|
2011-07-22 23:54:00 +02:00
|
|
|
|
|
|
|
if (l2cap_reassemble_sdu(chan, skb, control) == -EMSGSIZE)
|
|
|
|
l2cap_send_disconn_req(chan->conn, chan, ECONNRESET);
|
2009-08-21 03:26:01 +02:00
|
|
|
|
|
|
|
goto done;
|
|
|
|
|
2009-08-21 03:25:57 +02:00
|
|
|
default:
|
2011-04-13 22:20:49 +02:00
|
|
|
BT_DBG("chan %p: bad mode 0x%2.2x", chan, chan->mode);
|
2009-08-21 03:25:57 +02:00
|
|
|
break;
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
|
|
|
|
done:
|
2006-07-03 10:02:46 +02:00
|
|
|
if (sk)
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-07-29 09:16:36 +02:00
|
|
|
static inline int l2cap_conless_channel(struct l2cap_conn *conn, __le16 psm, struct sk_buff *skb)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2011-05-17 05:09:26 +02:00
|
|
|
struct sock *sk = NULL;
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *chan;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
chan = l2cap_global_chan_by_psm(0, psm, conn->src);
|
|
|
|
if (!chan)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
sk = chan->sk;
|
|
|
|
|
2010-11-01 19:43:53 +01:00
|
|
|
bh_lock_sock(sk);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
BT_DBG("sk %p, len %d", sk, skb->len);
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (chan->state != BT_BOUND && chan->state != BT_CONNECTED)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-06-18 03:46:27 +02:00
|
|
|
if (chan->imtu < skb->len)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-05-16 22:57:22 +02:00
|
|
|
if (!chan->ops->recv(chan->data, skb))
|
2005-04-17 00:20:36 +02:00
|
|
|
goto done;
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
|
|
|
|
done:
|
2009-04-20 06:31:08 +02:00
|
|
|
if (sk)
|
|
|
|
bh_unlock_sock(sk);
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-04-07 21:40:25 +02:00
|
|
|
static inline int l2cap_att_channel(struct l2cap_conn *conn, __le16 cid, struct sk_buff *skb)
|
|
|
|
{
|
2011-05-17 05:09:26 +02:00
|
|
|
struct sock *sk = NULL;
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *chan;
|
2011-04-07 21:40:25 +02:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
chan = l2cap_global_chan_by_scid(0, cid, conn->src);
|
|
|
|
if (!chan)
|
2011-04-07 21:40:25 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
sk = chan->sk;
|
|
|
|
|
2011-04-07 21:40:25 +02:00
|
|
|
bh_lock_sock(sk);
|
|
|
|
|
|
|
|
BT_DBG("sk %p, len %d", sk, skb->len);
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (chan->state != BT_BOUND && chan->state != BT_CONNECTED)
|
2011-04-07 21:40:25 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-06-18 03:46:27 +02:00
|
|
|
if (chan->imtu < skb->len)
|
2011-04-07 21:40:25 +02:00
|
|
|
goto drop;
|
|
|
|
|
2011-05-16 22:57:22 +02:00
|
|
|
if (!chan->ops->recv(chan->data, skb))
|
2011-04-07 21:40:25 +02:00
|
|
|
goto done;
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
|
|
|
|
done:
|
|
|
|
if (sk)
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct l2cap_hdr *lh = (void *) skb->data;
|
2007-07-29 09:16:36 +02:00
|
|
|
u16 cid, len;
|
|
|
|
__le16 psm;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
skb_pull(skb, L2CAP_HDR_SIZE);
|
|
|
|
cid = __le16_to_cpu(lh->cid);
|
|
|
|
len = __le16_to_cpu(lh->len);
|
|
|
|
|
2009-08-21 03:25:57 +02:00
|
|
|
if (len != skb->len) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
BT_DBG("len %d, cid 0x%4.4x", len, cid);
|
|
|
|
|
|
|
|
switch (cid) {
|
2011-02-11 22:28:54 +01:00
|
|
|
case L2CAP_CID_LE_SIGNALING:
|
2009-04-20 06:31:05 +02:00
|
|
|
case L2CAP_CID_SIGNALING:
|
2005-04-17 00:20:36 +02:00
|
|
|
l2cap_sig_channel(conn, skb);
|
|
|
|
break;
|
|
|
|
|
2009-04-20 06:31:05 +02:00
|
|
|
case L2CAP_CID_CONN_LESS:
|
2009-08-24 05:45:20 +02:00
|
|
|
psm = get_unaligned_le16(skb->data);
|
2005-04-17 00:20:36 +02:00
|
|
|
skb_pull(skb, 2);
|
|
|
|
l2cap_conless_channel(conn, psm, skb);
|
|
|
|
break;
|
|
|
|
|
2011-04-07 21:40:25 +02:00
|
|
|
case L2CAP_CID_LE_DATA:
|
|
|
|
l2cap_att_channel(conn, cid, skb);
|
|
|
|
break;
|
|
|
|
|
2011-06-07 23:46:31 +02:00
|
|
|
case L2CAP_CID_SMP:
|
|
|
|
if (smp_sig_channel(conn, skb))
|
|
|
|
l2cap_conn_del(conn->hcon, EACCES);
|
|
|
|
break;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
default:
|
|
|
|
l2cap_data_channel(conn, cid, skb);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---- L2CAP interface with lower layer (HCI) ---- */
|
|
|
|
|
|
|
|
static int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type)
|
|
|
|
{
|
|
|
|
int exact = 0, lm1 = 0, lm2 = 0;
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *c;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (type != ACL_LINK)
|
2010-06-22 18:56:28 +02:00
|
|
|
return -EINVAL;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr));
|
|
|
|
|
|
|
|
/* Find listening sockets and check their link_mode */
|
2011-04-27 23:26:32 +02:00
|
|
|
read_lock(&chan_list_lock);
|
|
|
|
list_for_each_entry(c, &chan_list, global_l) {
|
|
|
|
struct sock *sk = c->sk;
|
2011-04-12 23:31:57 +02:00
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (c->state != BT_LISTEN)
|
2005-04-17 00:20:36 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!bacmp(&bt_sk(sk)->src, &hdev->bdaddr)) {
|
2009-01-15 21:58:38 +01:00
|
|
|
lm1 |= HCI_LM_ACCEPT;
|
2011-10-11 13:04:34 +02:00
|
|
|
if (test_bit(FLAG_ROLE_SWITCH, &c->flags))
|
2009-01-15 21:58:38 +01:00
|
|
|
lm1 |= HCI_LM_MASTER;
|
2005-04-17 00:20:36 +02:00
|
|
|
exact++;
|
2009-01-15 21:58:38 +01:00
|
|
|
} else if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY)) {
|
|
|
|
lm2 |= HCI_LM_ACCEPT;
|
2011-10-11 13:04:34 +02:00
|
|
|
if (test_bit(FLAG_ROLE_SWITCH, &c->flags))
|
2009-01-15 21:58:38 +01:00
|
|
|
lm2 |= HCI_LM_MASTER;
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2011-04-27 23:26:32 +02:00
|
|
|
read_unlock(&chan_list_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return exact ? lm1 : lm2;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
|
|
|
|
{
|
2006-07-03 10:02:46 +02:00
|
|
|
struct l2cap_conn *conn;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status);
|
|
|
|
|
2011-02-11 02:38:49 +01:00
|
|
|
if (!(hcon->type == ACL_LINK || hcon->type == LE_LINK))
|
2010-06-22 18:56:28 +02:00
|
|
|
return -EINVAL;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (!status) {
|
|
|
|
conn = l2cap_conn_add(hcon, status);
|
|
|
|
if (conn)
|
|
|
|
l2cap_conn_ready(conn);
|
2006-07-03 10:02:46 +02:00
|
|
|
} else
|
2011-06-30 03:18:29 +02:00
|
|
|
l2cap_conn_del(hcon, bt_to_errno(status));
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-02-12 14:02:50 +01:00
|
|
|
static int l2cap_disconn_ind(struct hci_conn *hcon)
|
|
|
|
{
|
|
|
|
struct l2cap_conn *conn = hcon->l2cap_data;
|
|
|
|
|
|
|
|
BT_DBG("hcon %p", hcon);
|
|
|
|
|
2011-06-09 00:09:13 +02:00
|
|
|
if ((hcon->type != ACL_LINK && hcon->type != LE_LINK) || !conn)
|
2011-11-07 13:20:25 +01:00
|
|
|
return HCI_ERROR_REMOTE_USER_TERM;
|
2009-02-12 14:02:50 +01:00
|
|
|
|
|
|
|
return conn->disc_reason;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
BT_DBG("hcon %p reason %d", hcon, reason);
|
|
|
|
|
2011-02-11 02:38:49 +01:00
|
|
|
if (!(hcon->type == ACL_LINK || hcon->type == LE_LINK))
|
2010-06-22 18:56:28 +02:00
|
|
|
return -EINVAL;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-06-30 03:18:29 +02:00
|
|
|
l2cap_conn_del(hcon, bt_to_errno(reason));
|
2006-07-03 10:02:46 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-04-12 23:31:57 +02:00
|
|
|
static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt)
|
2009-01-15 21:58:44 +01:00
|
|
|
{
|
2011-05-02 22:13:55 +02:00
|
|
|
if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED)
|
2009-02-04 21:07:19 +01:00
|
|
|
return;
|
|
|
|
|
2009-01-15 21:58:44 +01:00
|
|
|
if (encrypt == 0x00) {
|
2011-04-12 23:31:57 +02:00
|
|
|
if (chan->sec_level == BT_SECURITY_MEDIUM) {
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2011-11-07 20:19:04 +01:00
|
|
|
__set_chan_timer(chan, L2CAP_ENC_TIMEOUT);
|
2011-04-12 23:31:57 +02:00
|
|
|
} else if (chan->sec_level == BT_SECURITY_HIGH)
|
2011-05-05 00:42:50 +02:00
|
|
|
l2cap_chan_close(chan, ECONNREFUSED);
|
2009-01-15 21:58:44 +01:00
|
|
|
} else {
|
2011-04-12 23:31:57 +02:00
|
|
|
if (chan->sec_level == BT_SECURITY_MEDIUM)
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2009-01-15 21:58:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-15 21:58:04 +01:00
|
|
|
static int l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2006-07-03 10:02:46 +02:00
|
|
|
struct l2cap_conn *conn = hcon->l2cap_data;
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-07-03 10:02:46 +02:00
|
|
|
if (!conn)
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
2006-07-03 10:02:46 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
BT_DBG("conn %p", conn);
|
|
|
|
|
2011-08-20 02:06:55 +02:00
|
|
|
if (hcon->type == LE_LINK) {
|
|
|
|
smp_distribute_keys(conn, 0);
|
|
|
|
del_timer(&conn->security_timer);
|
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
read_lock(&conn->chan_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
list_for_each_entry(chan, &conn->chan_l, list) {
|
2011-03-25 04:22:30 +01:00
|
|
|
struct sock *sk = chan->sk;
|
2011-03-31 21:17:41 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
bh_lock_sock(sk);
|
|
|
|
|
2011-01-27 01:42:57 +01:00
|
|
|
BT_DBG("chan->scid %d", chan->scid);
|
|
|
|
|
|
|
|
if (chan->scid == L2CAP_CID_LE_DATA) {
|
|
|
|
if (!status && encrypt) {
|
|
|
|
chan->sec_level = hcon->sec_level;
|
|
|
|
l2cap_chan_ready(sk);
|
|
|
|
}
|
|
|
|
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-06-10 22:02:12 +02:00
|
|
|
if (test_bit(CONF_CONNECT_PEND, &chan->conf_state)) {
|
2009-02-06 23:56:36 +01:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (!status && (chan->state == BT_CONNECTED ||
|
|
|
|
chan->state == BT_CONFIG)) {
|
2011-04-12 23:31:57 +02:00
|
|
|
l2cap_check_encryption(chan, encrypt);
|
2008-07-14 20:13:45 +02:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-06-03 05:19:47 +02:00
|
|
|
if (chan->state == BT_CONNECT) {
|
2008-07-14 20:13:54 +02:00
|
|
|
if (!status) {
|
|
|
|
struct l2cap_conn_req req;
|
2011-04-14 00:50:45 +02:00
|
|
|
req.scid = cpu_to_le16(chan->scid);
|
|
|
|
req.psm = chan->psm;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-03-25 17:59:37 +01:00
|
|
|
chan->ident = l2cap_get_ident(conn);
|
2011-06-10 22:02:12 +02:00
|
|
|
set_bit(CONF_CONNECT_PEND, &chan->conf_state);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-03-25 17:59:37 +01:00
|
|
|
l2cap_send_cmd(conn, chan->ident,
|
2008-07-14 20:13:54 +02:00
|
|
|
L2CAP_CONN_REQ, sizeof(req), &req);
|
|
|
|
} else {
|
2011-05-17 19:59:01 +02:00
|
|
|
__clear_chan_timer(chan);
|
2011-11-07 20:19:04 +01:00
|
|
|
__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
|
2008-07-14 20:13:54 +02:00
|
|
|
}
|
2011-06-03 05:19:47 +02:00
|
|
|
} else if (chan->state == BT_CONNECT2) {
|
2008-07-14 20:13:54 +02:00
|
|
|
struct l2cap_conn_rsp rsp;
|
2011-06-14 11:48:19 +02:00
|
|
|
__u16 res, stat;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2008-07-14 20:13:54 +02:00
|
|
|
if (!status) {
|
2011-06-14 11:48:19 +02:00
|
|
|
if (bt_sk(sk)->defer_setup) {
|
|
|
|
struct sock *parent = bt_sk(sk)->parent;
|
|
|
|
res = L2CAP_CR_PEND;
|
|
|
|
stat = L2CAP_CS_AUTHOR_PEND;
|
2011-07-15 20:30:21 +02:00
|
|
|
if (parent)
|
|
|
|
parent->sk_data_ready(parent, 0);
|
2011-06-14 11:48:19 +02:00
|
|
|
} else {
|
2011-06-21 19:52:56 +02:00
|
|
|
l2cap_state_change(chan, BT_CONFIG);
|
2011-06-14 11:48:19 +02:00
|
|
|
res = L2CAP_CR_SUCCESS;
|
|
|
|
stat = L2CAP_CS_NO_INFO;
|
|
|
|
}
|
2008-07-14 20:13:54 +02:00
|
|
|
} else {
|
2011-06-03 05:19:47 +02:00
|
|
|
l2cap_state_change(chan, BT_DISCONN);
|
2011-11-07 20:19:04 +01:00
|
|
|
__set_chan_timer(chan, L2CAP_DISC_TIMEOUT);
|
2011-06-14 11:48:19 +02:00
|
|
|
res = L2CAP_CR_SEC_BLOCK;
|
|
|
|
stat = L2CAP_CS_NO_INFO;
|
2008-07-14 20:13:54 +02:00
|
|
|
}
|
|
|
|
|
2011-04-14 00:50:45 +02:00
|
|
|
rsp.scid = cpu_to_le16(chan->dcid);
|
|
|
|
rsp.dcid = cpu_to_le16(chan->scid);
|
2011-06-14 11:48:19 +02:00
|
|
|
rsp.result = cpu_to_le16(res);
|
|
|
|
rsp.status = cpu_to_le16(stat);
|
2011-03-25 17:59:37 +01:00
|
|
|
l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_RSP,
|
|
|
|
sizeof(rsp), &rsp);
|
2008-07-14 20:13:54 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
bh_unlock_sock(sk);
|
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
read_unlock(&conn->chan_lock);
|
2008-07-14 20:13:54 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags)
|
|
|
|
{
|
|
|
|
struct l2cap_conn *conn = hcon->l2cap_data;
|
|
|
|
|
2011-01-11 16:20:20 +01:00
|
|
|
if (!conn)
|
|
|
|
conn = l2cap_conn_add(hcon, 0);
|
|
|
|
|
|
|
|
if (!conn)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto drop;
|
|
|
|
|
|
|
|
BT_DBG("conn %p len %d flags 0x%x", conn, skb->len, flags);
|
|
|
|
|
2011-01-03 10:14:36 +01:00
|
|
|
if (!(flags & ACL_CONT)) {
|
2005-04-17 00:20:36 +02:00
|
|
|
struct l2cap_hdr *hdr;
|
2011-03-25 04:22:30 +01:00
|
|
|
struct l2cap_chan *chan;
|
2010-09-15 13:28:44 +02:00
|
|
|
u16 cid;
|
2005-04-17 00:20:36 +02:00
|
|
|
int len;
|
|
|
|
|
|
|
|
if (conn->rx_len) {
|
|
|
|
BT_ERR("Unexpected start frame (len %d)", skb->len);
|
|
|
|
kfree_skb(conn->rx_skb);
|
|
|
|
conn->rx_skb = NULL;
|
|
|
|
conn->rx_len = 0;
|
|
|
|
l2cap_conn_unreliable(conn, ECOMM);
|
|
|
|
}
|
|
|
|
|
2010-09-15 13:28:43 +02:00
|
|
|
/* Start fragment always begin with Basic L2CAP header */
|
|
|
|
if (skb->len < L2CAP_HDR_SIZE) {
|
2005-04-17 00:20:36 +02:00
|
|
|
BT_ERR("Frame is too short (len %d)", skb->len);
|
|
|
|
l2cap_conn_unreliable(conn, ECOMM);
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdr = (struct l2cap_hdr *) skb->data;
|
|
|
|
len = __le16_to_cpu(hdr->len) + L2CAP_HDR_SIZE;
|
2010-09-15 13:28:44 +02:00
|
|
|
cid = __le16_to_cpu(hdr->cid);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (len == skb->len) {
|
|
|
|
/* Complete frame received */
|
|
|
|
l2cap_recv_frame(conn, skb);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
BT_DBG("Start: total len %d, frag len %d", len, skb->len);
|
|
|
|
|
|
|
|
if (skb->len > len) {
|
|
|
|
BT_ERR("Frame is too long (len %d, expected len %d)",
|
|
|
|
skb->len, len);
|
|
|
|
l2cap_conn_unreliable(conn, ECOMM);
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2011-03-31 21:17:41 +02:00
|
|
|
chan = l2cap_get_chan_by_scid(conn, cid);
|
2010-09-15 13:28:44 +02:00
|
|
|
|
2011-03-25 04:22:30 +01:00
|
|
|
if (chan && chan->sk) {
|
|
|
|
struct sock *sk = chan->sk;
|
2010-09-15 13:28:44 +02:00
|
|
|
|
2011-04-13 22:20:49 +02:00
|
|
|
if (chan->imtu < len - L2CAP_HDR_SIZE) {
|
2011-03-25 04:22:30 +01:00
|
|
|
BT_ERR("Frame exceeding recv MTU (len %d, "
|
|
|
|
"MTU %d)", len,
|
2011-04-13 22:20:49 +02:00
|
|
|
chan->imtu);
|
2011-03-25 04:22:30 +01:00
|
|
|
bh_unlock_sock(sk);
|
|
|
|
l2cap_conn_unreliable(conn, ECOMM);
|
|
|
|
goto drop;
|
|
|
|
}
|
2010-09-15 13:28:44 +02:00
|
|
|
bh_unlock_sock(sk);
|
2011-03-25 04:22:30 +01:00
|
|
|
}
|
2010-09-15 13:28:44 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Allocate skb for the complete frame (with header) */
|
2009-04-20 06:31:08 +02:00
|
|
|
conn->rx_skb = bt_skb_alloc(len, GFP_ATOMIC);
|
|
|
|
if (!conn->rx_skb)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto drop;
|
|
|
|
|
2007-03-27 23:55:52 +02:00
|
|
|
skb_copy_from_linear_data(skb, skb_put(conn->rx_skb, skb->len),
|
2009-02-09 09:18:02 +01:00
|
|
|
skb->len);
|
2005-04-17 00:20:36 +02:00
|
|
|
conn->rx_len = len - skb->len;
|
|
|
|
} else {
|
|
|
|
BT_DBG("Cont: frag len %d (expecting %d)", skb->len, conn->rx_len);
|
|
|
|
|
|
|
|
if (!conn->rx_len) {
|
|
|
|
BT_ERR("Unexpected continuation frame (len %d)", skb->len);
|
|
|
|
l2cap_conn_unreliable(conn, ECOMM);
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (skb->len > conn->rx_len) {
|
|
|
|
BT_ERR("Fragment is too long (len %d, expected %d)",
|
|
|
|
skb->len, conn->rx_len);
|
|
|
|
kfree_skb(conn->rx_skb);
|
|
|
|
conn->rx_skb = NULL;
|
|
|
|
conn->rx_len = 0;
|
|
|
|
l2cap_conn_unreliable(conn, ECOMM);
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2007-03-27 23:55:52 +02:00
|
|
|
skb_copy_from_linear_data(skb, skb_put(conn->rx_skb, skb->len),
|
2009-02-09 09:18:02 +01:00
|
|
|
skb->len);
|
2005-04-17 00:20:36 +02:00
|
|
|
conn->rx_len -= skb->len;
|
|
|
|
|
|
|
|
if (!conn->rx_len) {
|
|
|
|
/* Complete frame received */
|
|
|
|
l2cap_recv_frame(conn, conn->rx_skb);
|
|
|
|
conn->rx_skb = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-21 05:27:45 +01:00
|
|
|
static int l2cap_debugfs_show(struct seq_file *f, void *p)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2011-04-27 23:26:32 +02:00
|
|
|
struct l2cap_chan *c;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
read_lock_bh(&chan_list_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
list_for_each_entry(c, &chan_list, global_l) {
|
|
|
|
struct sock *sk = c->sk;
|
2010-03-15 22:12:58 +01:00
|
|
|
|
2011-02-10 17:16:06 +01:00
|
|
|
seq_printf(f, "%s %s %d %d 0x%4.4x 0x%4.4x %d %d %d %d\n",
|
2010-03-21 05:27:45 +01:00
|
|
|
batostr(&bt_sk(sk)->src),
|
|
|
|
batostr(&bt_sk(sk)->dst),
|
2011-06-03 05:19:47 +02:00
|
|
|
c->state, __le16_to_cpu(c->psm),
|
2011-04-27 23:26:32 +02:00
|
|
|
c->scid, c->dcid, c->imtu, c->omtu,
|
|
|
|
c->sec_level, c->mode);
|
2011-06-21 19:52:56 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2011-04-27 23:26:32 +02:00
|
|
|
read_unlock_bh(&chan_list_lock);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2010-03-21 05:27:45 +01:00
|
|
|
return 0;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2010-03-21 05:27:45 +01:00
|
|
|
static int l2cap_debugfs_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
return single_open(file, l2cap_debugfs_show, inode->i_private);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations l2cap_debugfs_fops = {
|
|
|
|
.open = l2cap_debugfs_open,
|
|
|
|
.read = seq_read,
|
|
|
|
.llseek = seq_lseek,
|
|
|
|
.release = single_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct dentry *l2cap_debugfs;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
static struct hci_proto l2cap_hci_proto = {
|
|
|
|
.name = "L2CAP",
|
|
|
|
.id = HCI_PROTO_L2CAP,
|
|
|
|
.connect_ind = l2cap_connect_ind,
|
|
|
|
.connect_cfm = l2cap_connect_cfm,
|
|
|
|
.disconn_ind = l2cap_disconn_ind,
|
2009-02-12 14:02:50 +01:00
|
|
|
.disconn_cfm = l2cap_disconn_cfm,
|
2009-01-15 21:58:04 +01:00
|
|
|
.security_cfm = l2cap_security_cfm,
|
2005-04-17 00:20:36 +02:00
|
|
|
.recv_acldata = l2cap_recv_acldata
|
|
|
|
};
|
|
|
|
|
2011-02-07 23:08:52 +01:00
|
|
|
int __init l2cap_init(void)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
int err;
|
2005-11-08 18:57:38 +01:00
|
|
|
|
2011-02-03 23:50:35 +01:00
|
|
|
err = l2cap_init_sockets();
|
2005-04-17 00:20:36 +02:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = hci_register_proto(&l2cap_hci_proto);
|
|
|
|
if (err < 0) {
|
|
|
|
BT_ERR("L2CAP protocol registration failed");
|
|
|
|
bt_sock_unregister(BTPROTO_L2CAP);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2010-03-21 05:27:45 +01:00
|
|
|
if (bt_debugfs) {
|
|
|
|
l2cap_debugfs = debugfs_create_file("l2cap", 0444,
|
|
|
|
bt_debugfs, NULL, &l2cap_debugfs_fops);
|
|
|
|
if (!l2cap_debugfs)
|
|
|
|
BT_ERR("Failed to create L2CAP debug file");
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
2011-02-03 23:50:35 +01:00
|
|
|
l2cap_cleanup_sockets();
|
2005-04-17 00:20:36 +02:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-02-07 23:08:52 +01:00
|
|
|
void l2cap_exit(void)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2010-03-21 05:27:45 +01:00
|
|
|
debugfs_remove(l2cap_debugfs);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (hci_unregister_proto(&l2cap_hci_proto) < 0)
|
|
|
|
BT_ERR("L2CAP protocol unregistration failed");
|
|
|
|
|
2011-02-03 23:50:35 +01:00
|
|
|
l2cap_cleanup_sockets();
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2010-07-18 21:25:54 +02:00
|
|
|
module_param(disable_ertm, bool, 0644);
|
|
|
|
MODULE_PARM_DESC(disable_ertm, "Disable enhanced retransmission mode");
|
2011-09-16 15:26:32 +02:00
|
|
|
|
|
|
|
module_param(enable_hs, bool, 0644);
|
|
|
|
MODULE_PARM_DESC(enable_hs, "Enable High Speed");
|