|
@@ -62,9 +62,8 @@ static u8 l2cap_fixed_chan[8] = { 0x02, };
|
|
|
|
|
|
static struct workqueue_struct *_busy_wq;
|
|
|
|
|
|
-struct bt_sock_list l2cap_sk_list = {
|
|
|
- .lock = __RW_LOCK_UNLOCKED(l2cap_sk_list.lock)
|
|
|
-};
|
|
|
+LIST_HEAD(chan_list);
|
|
|
+DEFINE_RWLOCK(chan_list_lock);
|
|
|
|
|
|
static void l2cap_busy_work(struct work_struct *work);
|
|
|
|
|
@@ -135,6 +134,64 @@ static inline struct l2cap_chan *l2cap_get_chan_by_ident(struct l2cap_conn *conn
|
|
|
return c;
|
|
|
}
|
|
|
|
|
|
+static struct l2cap_chan *__l2cap_global_chan_by_addr(__le16 psm, bdaddr_t *src)
|
|
|
+{
|
|
|
+ struct l2cap_chan *c;
|
|
|
+
|
|
|
+ list_for_each_entry(c, &chan_list, global_l) {
|
|
|
+ if (c->sport == psm && !bacmp(&bt_sk(c->sk)->src, src))
|
|
|
+ goto found;
|
|
|
+ }
|
|
|
+
|
|
|
+ c = NULL;
|
|
|
+found:
|
|
|
+ return c;
|
|
|
+}
|
|
|
+
|
|
|
+int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm)
|
|
|
+{
|
|
|
+ int err;
|
|
|
+
|
|
|
+ write_lock_bh(&chan_list_lock);
|
|
|
+
|
|
|
+ if (psm && __l2cap_global_chan_by_addr(psm, src)) {
|
|
|
+ err = -EADDRINUSE;
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (psm) {
|
|
|
+ chan->psm = psm;
|
|
|
+ chan->sport = psm;
|
|
|
+ err = 0;
|
|
|
+ } else {
|
|
|
+ u16 p;
|
|
|
+
|
|
|
+ err = -EINVAL;
|
|
|
+ for (p = 0x1001; p < 0x1100; p += 2)
|
|
|
+ if (!__l2cap_global_chan_by_addr(cpu_to_le16(p), src)) {
|
|
|
+ chan->psm = cpu_to_le16(p);
|
|
|
+ chan->sport = cpu_to_le16(p);
|
|
|
+ err = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+done:
|
|
|
+ write_unlock_bh(&chan_list_lock);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid)
|
|
|
+{
|
|
|
+ write_lock_bh(&chan_list_lock);
|
|
|
+
|
|
|
+ chan->scid = scid;
|
|
|
+
|
|
|
+ write_unlock_bh(&chan_list_lock);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static u16 l2cap_alloc_cid(struct l2cap_conn *conn)
|
|
|
{
|
|
|
u16 cid = L2CAP_CID_DYN_START;
|
|
@@ -147,7 +204,7 @@ static u16 l2cap_alloc_cid(struct l2cap_conn *conn)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-struct l2cap_chan *l2cap_chan_alloc(struct sock *sk)
|
|
|
+struct l2cap_chan *l2cap_chan_create(struct sock *sk)
|
|
|
{
|
|
|
struct l2cap_chan *chan;
|
|
|
|
|
@@ -157,11 +214,19 @@ struct l2cap_chan *l2cap_chan_alloc(struct sock *sk)
|
|
|
|
|
|
chan->sk = sk;
|
|
|
|
|
|
+ write_lock_bh(&chan_list_lock);
|
|
|
+ list_add(&chan->global_l, &chan_list);
|
|
|
+ write_unlock_bh(&chan_list_lock);
|
|
|
+
|
|
|
return chan;
|
|
|
}
|
|
|
|
|
|
-void l2cap_chan_free(struct l2cap_chan *chan)
|
|
|
+void l2cap_chan_destroy(struct l2cap_chan *chan)
|
|
|
{
|
|
|
+ write_lock_bh(&chan_list_lock);
|
|
|
+ list_del(&chan->global_l);
|
|
|
+ write_unlock_bh(&chan_list_lock);
|
|
|
+
|
|
|
kfree(chan);
|
|
|
}
|
|
|
|
|
@@ -591,48 +656,51 @@ static void l2cap_conn_start(struct l2cap_conn *conn)
|
|
|
/* Find socket with cid and source bdaddr.
|
|
|
* Returns closest match, locked.
|
|
|
*/
|
|
|
-static struct sock *l2cap_get_sock_by_scid(int state, __le16 cid, bdaddr_t *src)
|
|
|
+static struct l2cap_chan *l2cap_global_chan_by_scid(int state, __le16 cid, bdaddr_t *src)
|
|
|
{
|
|
|
- struct sock *sk = NULL, *sk1 = NULL;
|
|
|
- struct hlist_node *node;
|
|
|
+ struct l2cap_chan *c, *c1 = NULL;
|
|
|
|
|
|
- read_lock(&l2cap_sk_list.lock);
|
|
|
+ read_lock(&chan_list_lock);
|
|
|
|
|
|
- sk_for_each(sk, node, &l2cap_sk_list.head) {
|
|
|
- struct l2cap_chan *chan = l2cap_pi(sk)->chan;
|
|
|
+ list_for_each_entry(c, &chan_list, global_l) {
|
|
|
+ struct sock *sk = c->sk;
|
|
|
|
|
|
if (state && sk->sk_state != state)
|
|
|
continue;
|
|
|
|
|
|
- if (chan->scid == cid) {
|
|
|
+ if (c->scid == cid) {
|
|
|
/* Exact match. */
|
|
|
- if (!bacmp(&bt_sk(sk)->src, src))
|
|
|
- break;
|
|
|
+ if (!bacmp(&bt_sk(sk)->src, src)) {
|
|
|
+ read_unlock(&chan_list_lock);
|
|
|
+ return c;
|
|
|
+ }
|
|
|
|
|
|
/* Closest match */
|
|
|
if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY))
|
|
|
- sk1 = sk;
|
|
|
+ c1 = c;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- read_unlock(&l2cap_sk_list.lock);
|
|
|
+ read_unlock(&chan_list_lock);
|
|
|
|
|
|
- return node ? sk : sk1;
|
|
|
+ return c1;
|
|
|
}
|
|
|
|
|
|
static void l2cap_le_conn_ready(struct l2cap_conn *conn)
|
|
|
{
|
|
|
struct sock *parent, *sk;
|
|
|
- struct l2cap_chan *chan;
|
|
|
+ struct l2cap_chan *chan, *pchan;
|
|
|
|
|
|
BT_DBG("");
|
|
|
|
|
|
/* Check if we have socket listening on cid */
|
|
|
- parent = l2cap_get_sock_by_scid(BT_LISTEN, L2CAP_CID_LE_DATA,
|
|
|
+ pchan = l2cap_global_chan_by_scid(BT_LISTEN, L2CAP_CID_LE_DATA,
|
|
|
conn->src);
|
|
|
- if (!parent)
|
|
|
+ if (!pchan)
|
|
|
return;
|
|
|
|
|
|
+ parent = pchan->sk;
|
|
|
+
|
|
|
bh_lock_sock(parent);
|
|
|
|
|
|
/* Check for backlog size */
|
|
@@ -645,7 +713,7 @@ static void l2cap_le_conn_ready(struct l2cap_conn *conn)
|
|
|
if (!sk)
|
|
|
goto clean;
|
|
|
|
|
|
- chan = l2cap_chan_alloc(sk);
|
|
|
+ chan = l2cap_chan_create(sk);
|
|
|
if (!chan) {
|
|
|
l2cap_sock_kill(sk);
|
|
|
goto clean;
|
|
@@ -823,33 +891,34 @@ static inline void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *ch
|
|
|
/* Find socket with psm and source bdaddr.
|
|
|
* Returns closest match.
|
|
|
*/
|
|
|
-static struct sock *l2cap_get_sock_by_psm(int state, __le16 psm, bdaddr_t *src)
|
|
|
+static struct l2cap_chan *l2cap_global_chan_by_psm(int state, __le16 psm, bdaddr_t *src)
|
|
|
{
|
|
|
- struct sock *sk = NULL, *sk1 = NULL;
|
|
|
- struct hlist_node *node;
|
|
|
+ struct l2cap_chan *c, *c1 = NULL;
|
|
|
|
|
|
- read_lock(&l2cap_sk_list.lock);
|
|
|
+ read_lock(&chan_list_lock);
|
|
|
|
|
|
- sk_for_each(sk, node, &l2cap_sk_list.head) {
|
|
|
- struct l2cap_chan *chan = l2cap_pi(sk)->chan;
|
|
|
+ list_for_each_entry(c, &chan_list, global_l) {
|
|
|
+ struct sock *sk = c->sk;
|
|
|
|
|
|
if (state && sk->sk_state != state)
|
|
|
continue;
|
|
|
|
|
|
- if (chan->psm == psm) {
|
|
|
+ if (c->psm == psm) {
|
|
|
/* Exact match. */
|
|
|
- if (!bacmp(&bt_sk(sk)->src, src))
|
|
|
- break;
|
|
|
+ if (!bacmp(&bt_sk(sk)->src, src)) {
|
|
|
+ read_unlock_bh(&chan_list_lock);
|
|
|
+ return c;
|
|
|
+ }
|
|
|
|
|
|
/* Closest match */
|
|
|
if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY))
|
|
|
- sk1 = sk;
|
|
|
+ c1 = c;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- read_unlock(&l2cap_sk_list.lock);
|
|
|
+ read_unlock(&chan_list_lock);
|
|
|
|
|
|
- return node ? sk : sk1;
|
|
|
+ return c1;
|
|
|
}
|
|
|
|
|
|
int l2cap_chan_connect(struct l2cap_chan *chan)
|
|
@@ -2019,7 +2088,7 @@ static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hd
|
|
|
{
|
|
|
struct l2cap_conn_req *req = (struct l2cap_conn_req *) data;
|
|
|
struct l2cap_conn_rsp rsp;
|
|
|
- struct l2cap_chan *chan = NULL;
|
|
|
+ struct l2cap_chan *chan = NULL, *pchan;
|
|
|
struct sock *parent, *sk = NULL;
|
|
|
int result, status = L2CAP_CS_NO_INFO;
|
|
|
|
|
@@ -2029,12 +2098,14 @@ static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hd
|
|
|
BT_DBG("psm 0x%2.2x scid 0x%4.4x", psm, scid);
|
|
|
|
|
|
/* Check if we have socket listening on psm */
|
|
|
- parent = l2cap_get_sock_by_psm(BT_LISTEN, psm, conn->src);
|
|
|
- if (!parent) {
|
|
|
+ pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, conn->src);
|
|
|
+ if (!pchan) {
|
|
|
result = L2CAP_CR_BAD_PSM;
|
|
|
goto sendresp;
|
|
|
}
|
|
|
|
|
|
+ parent = pchan->sk;
|
|
|
+
|
|
|
bh_lock_sock(parent);
|
|
|
|
|
|
/* Check if the ACL is secure enough (if not SDP) */
|
|
@@ -2057,7 +2128,7 @@ static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hd
|
|
|
if (!sk)
|
|
|
goto response;
|
|
|
|
|
|
- chan = l2cap_chan_alloc(sk);
|
|
|
+ chan = l2cap_chan_create(sk);
|
|
|
if (!chan) {
|
|
|
l2cap_sock_kill(sk);
|
|
|
goto response;
|
|
@@ -3685,11 +3756,14 @@ done:
|
|
|
static inline int l2cap_conless_channel(struct l2cap_conn *conn, __le16 psm, struct sk_buff *skb)
|
|
|
{
|
|
|
struct sock *sk;
|
|
|
+ struct l2cap_chan *chan;
|
|
|
|
|
|
- sk = l2cap_get_sock_by_psm(0, psm, conn->src);
|
|
|
- if (!sk)
|
|
|
+ chan = l2cap_global_chan_by_psm(0, psm, conn->src);
|
|
|
+ if (!chan)
|
|
|
goto drop;
|
|
|
|
|
|
+ sk = chan->sk;
|
|
|
+
|
|
|
bh_lock_sock(sk);
|
|
|
|
|
|
BT_DBG("sk %p, len %d", sk, skb->len);
|
|
@@ -3715,11 +3789,14 @@ done:
|
|
|
static inline int l2cap_att_channel(struct l2cap_conn *conn, __le16 cid, struct sk_buff *skb)
|
|
|
{
|
|
|
struct sock *sk;
|
|
|
+ struct l2cap_chan *chan;
|
|
|
|
|
|
- sk = l2cap_get_sock_by_scid(0, cid, conn->src);
|
|
|
- if (!sk)
|
|
|
+ chan = l2cap_global_chan_by_scid(0, cid, conn->src);
|
|
|
+ if (!chan)
|
|
|
goto drop;
|
|
|
|
|
|
+ sk = chan->sk;
|
|
|
+
|
|
|
bh_lock_sock(sk);
|
|
|
|
|
|
BT_DBG("sk %p, len %d", sk, skb->len);
|
|
@@ -3786,8 +3863,7 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
|
|
|
static int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type)
|
|
|
{
|
|
|
int exact = 0, lm1 = 0, lm2 = 0;
|
|
|
- register struct sock *sk;
|
|
|
- struct hlist_node *node;
|
|
|
+ struct l2cap_chan *c;
|
|
|
|
|
|
if (type != ACL_LINK)
|
|
|
return -EINVAL;
|
|
@@ -3795,25 +3871,25 @@ static int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type)
|
|
|
BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr));
|
|
|
|
|
|
/* Find listening sockets and check their link_mode */
|
|
|
- read_lock(&l2cap_sk_list.lock);
|
|
|
- sk_for_each(sk, node, &l2cap_sk_list.head) {
|
|
|
- struct l2cap_chan *chan = l2cap_pi(sk)->chan;
|
|
|
+ read_lock(&chan_list_lock);
|
|
|
+ list_for_each_entry(c, &chan_list, global_l) {
|
|
|
+ struct sock *sk = c->sk;
|
|
|
|
|
|
if (sk->sk_state != BT_LISTEN)
|
|
|
continue;
|
|
|
|
|
|
if (!bacmp(&bt_sk(sk)->src, &hdev->bdaddr)) {
|
|
|
lm1 |= HCI_LM_ACCEPT;
|
|
|
- if (chan->role_switch)
|
|
|
+ if (c->role_switch)
|
|
|
lm1 |= HCI_LM_MASTER;
|
|
|
exact++;
|
|
|
} else if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY)) {
|
|
|
lm2 |= HCI_LM_ACCEPT;
|
|
|
- if (chan->role_switch)
|
|
|
+ if (c->role_switch)
|
|
|
lm2 |= HCI_LM_MASTER;
|
|
|
}
|
|
|
}
|
|
|
- read_unlock(&l2cap_sk_list.lock);
|
|
|
+ read_unlock(&chan_list_lock);
|
|
|
|
|
|
return exact ? lm1 : lm2;
|
|
|
}
|
|
@@ -4066,25 +4142,22 @@ drop:
|
|
|
|
|
|
static int l2cap_debugfs_show(struct seq_file *f, void *p)
|
|
|
{
|
|
|
- struct sock *sk;
|
|
|
- struct hlist_node *node;
|
|
|
+ struct l2cap_chan *c;
|
|
|
|
|
|
- read_lock_bh(&l2cap_sk_list.lock);
|
|
|
+ read_lock_bh(&chan_list_lock);
|
|
|
|
|
|
- sk_for_each(sk, node, &l2cap_sk_list.head) {
|
|
|
- struct l2cap_pinfo *pi = l2cap_pi(sk);
|
|
|
- struct l2cap_chan *chan = pi->chan;
|
|
|
+ list_for_each_entry(c, &chan_list, global_l) {
|
|
|
+ struct sock *sk = c->sk;
|
|
|
|
|
|
seq_printf(f, "%s %s %d %d 0x%4.4x 0x%4.4x %d %d %d %d\n",
|
|
|
batostr(&bt_sk(sk)->src),
|
|
|
batostr(&bt_sk(sk)->dst),
|
|
|
- sk->sk_state, __le16_to_cpu(chan->psm),
|
|
|
- chan->scid, chan->dcid,
|
|
|
- chan->imtu, chan->omtu, chan->sec_level,
|
|
|
- chan->mode);
|
|
|
+ sk->sk_state, __le16_to_cpu(c->psm),
|
|
|
+ c->scid, c->dcid, c->imtu, c->omtu,
|
|
|
+ c->sec_level, c->mode);
|
|
|
}
|
|
|
|
|
|
- read_unlock_bh(&l2cap_sk_list.lock);
|
|
|
+ read_unlock_bh(&chan_list_lock);
|
|
|
|
|
|
return 0;
|
|
|
}
|