URL
https://opencores.org/ocsvn/or1k/or1k/trunk
Subversion Repositories or1k
Compare Revisions
- This comparison shows the changes necessary to convert path
/or1k/trunk/linux/linux-2.4/net/bluetooth
- from Rev 1275 to Rev 1765
- ↔ Reverse comparison
Rev 1275 → Rev 1765
/hci_conn.c
0,0 → 1,435
/* |
BlueZ - Bluetooth protocol stack for Linux |
Copyright (C) 2000-2001 Qualcomm Incorporated |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* HCI Connection handling. |
* |
* $Id: hci_conn.c,v 1.1.1.1 2004-04-15 01:17:03 phoenix Exp $ |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/init.h> |
#include <linux/skbuff.h> |
#include <linux/interrupt.h> |
#include <linux/notifier.h> |
#include <net/sock.h> |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
#include <asm/unaligned.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/hci_core.h> |
|
#ifndef HCI_CORE_DEBUG |
#undef BT_DBG |
#define BT_DBG( A... ) |
#endif |
|
void hci_acl_connect(struct hci_conn *conn) |
{ |
struct hci_dev *hdev = conn->hdev; |
struct inquiry_entry *ie; |
create_conn_cp cp; |
|
BT_DBG("%p", conn); |
|
conn->state = BT_CONNECT; |
conn->out = 1; |
conn->link_mode = HCI_LM_MASTER; |
|
memset(&cp, 0, sizeof(cp)); |
bacpy(&cp.bdaddr, &conn->dst); |
cp.pscan_rep_mode = 0x02; |
|
if ((ie = inquiry_cache_lookup(hdev, &conn->dst)) && |
inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) { |
cp.pscan_rep_mode = ie->info.pscan_rep_mode; |
cp.pscan_mode = ie->info.pscan_mode; |
cp.clock_offset = ie->info.clock_offset | __cpu_to_le16(0x8000); |
} |
|
cp.pkt_type = __cpu_to_le16(hdev->pkt_type & ACL_PTYPE_MASK); |
if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER)) |
cp.role_switch = 0x01; |
else |
cp.role_switch = 0x00; |
|
hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CREATE_CONN, |
CREATE_CONN_CP_SIZE, &cp); |
} |
|
void hci_acl_disconn(struct hci_conn *conn, __u8 reason) |
{ |
disconnect_cp cp; |
|
BT_DBG("%p", conn); |
|
conn->state = BT_DISCONN; |
|
cp.handle = __cpu_to_le16(conn->handle); |
cp.reason = reason; |
hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_DISCONNECT, |
DISCONNECT_CP_SIZE, &cp); |
} |
|
void hci_add_sco(struct hci_conn *conn, __u16 handle) |
{ |
struct hci_dev *hdev = conn->hdev; |
add_sco_cp cp; |
|
BT_DBG("%p", conn); |
|
conn->state = BT_CONNECT; |
conn->out = 1; |
|
cp.pkt_type = __cpu_to_le16(hdev->pkt_type & SCO_PTYPE_MASK); |
cp.handle = __cpu_to_le16(handle); |
|
hci_send_cmd(hdev, OGF_LINK_CTL, OCF_ADD_SCO, ADD_SCO_CP_SIZE, &cp); |
} |
|
static void hci_conn_timeout(unsigned long arg) |
{ |
struct hci_conn *conn = (void *)arg; |
struct hci_dev *hdev = conn->hdev; |
|
BT_DBG("conn %p state %d", conn, conn->state); |
|
if (atomic_read(&conn->refcnt)) |
return; |
|
hci_dev_lock(hdev); |
if (conn->state == BT_CONNECTED) |
hci_acl_disconn(conn, 0x13); |
else |
conn->state = BT_CLOSED; |
hci_dev_unlock(hdev); |
return; |
} |
|
static void hci_conn_init_timer(struct hci_conn *conn) |
{ |
init_timer(&conn->timer); |
conn->timer.function = hci_conn_timeout; |
conn->timer.data = (unsigned long)conn; |
} |
|
struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst) |
{ |
struct hci_conn *conn; |
|
BT_DBG("%s dst %s", hdev->name, batostr(dst)); |
|
if (!(conn = kmalloc(sizeof(struct hci_conn), GFP_ATOMIC))) |
return NULL; |
memset(conn, 0, sizeof(struct hci_conn)); |
|
bacpy(&conn->dst, dst); |
conn->type = type; |
conn->hdev = hdev; |
conn->state = BT_OPEN; |
|
skb_queue_head_init(&conn->data_q); |
hci_conn_init_timer(conn); |
|
atomic_set(&conn->refcnt, 0); |
|
hci_dev_hold(hdev); |
|
tasklet_disable(&hdev->tx_task); |
conn_hash_add(hdev, conn); |
tasklet_enable(&hdev->tx_task); |
|
return conn; |
} |
|
int hci_conn_del(struct hci_conn *conn) |
{ |
struct hci_dev *hdev = conn->hdev; |
|
BT_DBG("%s conn %p handle %d", hdev->name, conn, conn->handle); |
|
hci_conn_del_timer(conn); |
|
if (conn->type == SCO_LINK) { |
struct hci_conn *acl = conn->link; |
if (acl) { |
acl->link = NULL; |
hci_conn_put(acl); |
} |
} else { |
struct hci_conn *sco = conn->link; |
if (sco) |
sco->link = NULL; |
|
/* Unacked frames */ |
hdev->acl_cnt += conn->sent; |
} |
|
tasklet_disable(&hdev->tx_task); |
conn_hash_del(hdev, conn); |
tasklet_enable(&hdev->tx_task); |
|
skb_queue_purge(&conn->data_q); |
|
hci_dev_put(hdev); |
|
kfree(conn); |
return 0; |
} |
|
struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src) |
{ |
int use_src = bacmp(src, BDADDR_ANY); |
struct hci_dev *hdev = NULL; |
struct list_head *p; |
|
BT_DBG("%s -> %s", batostr(src), batostr(dst)); |
|
read_lock_bh(&hdev_list_lock); |
|
list_for_each(p, &hdev_list) { |
struct hci_dev *d; |
d = list_entry(p, struct hci_dev, list); |
|
if (!test_bit(HCI_UP, &d->flags)) |
continue; |
|
/* Simple routing: |
* No source address - find interface with bdaddr != dst |
* Source address - find interface with bdaddr == src |
*/ |
|
if (use_src) { |
if (!bacmp(&d->bdaddr, src)) { |
hdev = d; break; |
} |
} else { |
if (bacmp(&d->bdaddr, dst)) { |
hdev = d; break; |
} |
} |
} |
|
if (hdev) |
hci_dev_hold(hdev); |
|
read_unlock_bh(&hdev_list_lock); |
return hdev; |
} |
|
/* Create SCO or ACL connection. |
* Device _must_ be locked */ |
struct hci_conn * hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst) |
{ |
struct hci_conn *acl; |
|
BT_DBG("%s dst %s", hdev->name, batostr(dst)); |
|
if (!(acl = conn_hash_lookup_ba(hdev, ACL_LINK, dst))) { |
if (!(acl = hci_conn_add(hdev, ACL_LINK, dst))) |
return NULL; |
} |
|
hci_conn_hold(acl); |
|
if (acl->state == BT_OPEN || acl->state == BT_CLOSED) |
hci_acl_connect(acl); |
|
if (type == SCO_LINK) { |
struct hci_conn *sco; |
|
if (!(sco = conn_hash_lookup_ba(hdev, SCO_LINK, dst))) { |
if (!(sco = hci_conn_add(hdev, SCO_LINK, dst))) { |
hci_conn_put(acl); |
return NULL; |
} |
} |
acl->link = sco; |
sco->link = acl; |
|
hci_conn_hold(sco); |
|
if (acl->state == BT_CONNECTED && |
(sco->state == BT_OPEN || sco->state == BT_CLOSED)) |
hci_add_sco(sco, acl->handle); |
|
return sco; |
} else { |
return acl; |
} |
} |
|
/* Authenticate remote device */ |
int hci_conn_auth(struct hci_conn *conn) |
{ |
BT_DBG("conn %p", conn); |
|
if (conn->link_mode & HCI_LM_AUTH) |
return 1; |
|
if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) { |
auth_requested_cp ar; |
ar.handle = __cpu_to_le16(conn->handle); |
hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_AUTH_REQUESTED, |
AUTH_REQUESTED_CP_SIZE, &ar); |
} |
return 0; |
} |
|
/* Enable encryption */ |
int hci_conn_encrypt(struct hci_conn *conn) |
{ |
BT_DBG("conn %p", conn); |
|
if (conn->link_mode & HCI_LM_ENCRYPT) |
return 1; |
|
if (test_and_set_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) |
return 0; |
|
if (hci_conn_auth(conn)) { |
set_conn_encrypt_cp ce; |
ce.handle = __cpu_to_le16(conn->handle); |
ce.encrypt = 1; |
hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT, |
SET_CONN_ENCRYPT_CP_SIZE, &ce); |
} |
return 0; |
} |
|
/* Drop all connection on the device */ |
void hci_conn_hash_flush(struct hci_dev *hdev) |
{ |
struct conn_hash *h = &hdev->conn_hash; |
struct list_head *p; |
|
BT_DBG("hdev %s", hdev->name); |
|
p = h->list.next; |
while (p != &h->list) { |
struct hci_conn *c; |
|
c = list_entry(p, struct hci_conn, list); |
p = p->next; |
|
c->state = BT_CLOSED; |
|
hci_proto_disconn_ind(c, 0x16); |
hci_conn_del(c); |
} |
} |
|
int hci_get_conn_list(unsigned long arg) |
{ |
struct hci_conn_list_req req, *cl; |
struct hci_conn_info *ci; |
struct hci_dev *hdev; |
struct list_head *p; |
int n = 0, size, err; |
|
if (copy_from_user(&req, (void *) arg, sizeof(req))) |
return -EFAULT; |
|
if (!req.conn_num || req.conn_num > (PAGE_SIZE * 2) / sizeof(*ci)) |
return -EINVAL; |
|
size = sizeof(req) + req.conn_num * sizeof(*ci); |
|
if (!(cl = (void *) kmalloc(size, GFP_KERNEL))) |
return -ENOMEM; |
|
if (!(hdev = hci_dev_get(req.dev_id))) { |
kfree(cl); |
return -ENODEV; |
} |
|
ci = cl->conn_info; |
|
hci_dev_lock_bh(hdev); |
list_for_each(p, &hdev->conn_hash.list) { |
register struct hci_conn *c; |
c = list_entry(p, struct hci_conn, list); |
|
bacpy(&(ci + n)->bdaddr, &c->dst); |
(ci + n)->handle = c->handle; |
(ci + n)->type = c->type; |
(ci + n)->out = c->out; |
(ci + n)->state = c->state; |
(ci + n)->link_mode = c->link_mode; |
if (++n >= req.conn_num) |
break; |
} |
hci_dev_unlock_bh(hdev); |
|
cl->dev_id = hdev->id; |
cl->conn_num = n; |
size = sizeof(req) + n * sizeof(*ci); |
|
hci_dev_put(hdev); |
|
err = copy_to_user((void *) arg, cl, size); |
kfree(cl); |
|
return err ? -EFAULT : 0; |
} |
|
int hci_get_conn_info(struct hci_dev *hdev, unsigned long arg) |
{ |
struct hci_conn_info_req req; |
struct hci_conn_info ci; |
struct hci_conn *conn; |
char *ptr = (void *) arg + sizeof(req); |
|
if (copy_from_user(&req, (void *) arg, sizeof(req))) |
return -EFAULT; |
|
hci_dev_lock_bh(hdev); |
conn = conn_hash_lookup_ba(hdev, req.type, &req.bdaddr); |
if (conn) { |
bacpy(&ci.bdaddr, &conn->dst); |
ci.handle = conn->handle; |
ci.type = conn->type; |
ci.out = conn->out; |
ci.state = conn->state; |
ci.link_mode = conn->link_mode; |
} |
hci_dev_unlock_bh(hdev); |
|
if (!conn) |
return -ENOENT; |
|
return copy_to_user(ptr, &ci, sizeof(ci)) ? -EFAULT : 0; |
} |
/l2cap.c
0,0 → 1,2187
/* |
BlueZ - Bluetooth protocol stack for Linux |
Copyright (C) 2000-2001 Qualcomm Incorporated |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* BlueZ L2CAP core and sockets. |
* |
* $Id: l2cap.c,v 1.1.1.1 2004-04-15 01:17:03 phoenix Exp $ |
*/ |
#define VERSION "2.3" |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/init.h> |
#include <linux/skbuff.h> |
#include <linux/interrupt.h> |
#include <linux/socket.h> |
#include <linux/skbuff.h> |
#include <linux/proc_fs.h> |
#include <linux/list.h> |
#include <net/sock.h> |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
#include <asm/unaligned.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/hci_core.h> |
#include <net/bluetooth/l2cap.h> |
|
#ifndef L2CAP_DEBUG |
#undef BT_DBG |
#define BT_DBG( A... ) |
#endif |
|
static struct proto_ops l2cap_sock_ops; |
|
struct bluez_sock_list l2cap_sk_list = { |
lock: RW_LOCK_UNLOCKED |
}; |
|
static int l2cap_conn_del(struct hci_conn *conn, int err); |
|
static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent); |
static void l2cap_chan_del(struct sock *sk, int err); |
static int l2cap_chan_send(struct sock *sk, struct msghdr *msg, int len); |
|
static void __l2cap_sock_close(struct sock *sk, int reason); |
static void l2cap_sock_close(struct sock *sk); |
static void l2cap_sock_kill(struct sock *sk); |
|
static int l2cap_send_req(struct l2cap_conn *conn, __u8 code, __u16 len, void *data); |
static int l2cap_send_rsp(struct l2cap_conn *conn, __u8 ident, __u8 code, __u16 len, void *data); |
|
/* ----- L2CAP timers ------ */ |
static void l2cap_sock_timeout(unsigned long arg) |
{ |
struct sock *sk = (struct sock *) arg; |
|
BT_DBG("sock %p state %d", sk, sk->state); |
|
bh_lock_sock(sk); |
__l2cap_sock_close(sk, ETIMEDOUT); |
bh_unlock_sock(sk); |
|
l2cap_sock_kill(sk); |
sock_put(sk); |
} |
|
static void l2cap_sock_set_timer(struct sock *sk, long timeout) |
{ |
BT_DBG("sk %p state %d timeout %ld", sk, sk->state, timeout); |
|
if (!mod_timer(&sk->timer, jiffies + timeout)) |
sock_hold(sk); |
} |
|
static void l2cap_sock_clear_timer(struct sock *sk) |
{ |
BT_DBG("sock %p state %d", sk, sk->state); |
|
if (timer_pending(&sk->timer) && del_timer(&sk->timer)) |
__sock_put(sk); |
} |
|
static void l2cap_sock_init_timer(struct sock *sk) |
{ |
init_timer(&sk->timer); |
sk->timer.function = l2cap_sock_timeout; |
sk->timer.data = (unsigned long)sk; |
} |
|
/* -------- L2CAP connections --------- */ |
static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, __u8 status) |
{ |
struct l2cap_conn *conn; |
|
if ((conn = hcon->l2cap_data)) |
return conn; |
|
if (status) |
return conn; |
|
if (!(conn = kmalloc(sizeof(struct l2cap_conn), GFP_ATOMIC))) |
return NULL; |
memset(conn, 0, sizeof(struct l2cap_conn)); |
|
hcon->l2cap_data = conn; |
conn->hcon = hcon; |
|
conn->mtu = hcon->hdev->acl_mtu; |
conn->src = &hcon->hdev->bdaddr; |
conn->dst = &hcon->dst; |
|
spin_lock_init(&conn->lock); |
conn->chan_list.lock = RW_LOCK_UNLOCKED; |
|
BT_DBG("hcon %p conn %p", hcon, conn); |
|
MOD_INC_USE_COUNT; |
return conn; |
} |
|
static int l2cap_conn_del(struct hci_conn *hcon, int err) |
{ |
struct l2cap_conn *conn; |
struct sock *sk; |
|
if (!(conn = hcon->l2cap_data)) |
return 0; |
|
BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); |
|
if (conn->rx_skb) |
kfree_skb(conn->rx_skb); |
|
/* Kill channels */ |
while ((sk = conn->chan_list.head)) { |
bh_lock_sock(sk); |
l2cap_chan_del(sk, err); |
bh_unlock_sock(sk); |
l2cap_sock_kill(sk); |
} |
|
hcon->l2cap_data = NULL; |
kfree(conn); |
|
MOD_DEC_USE_COUNT; |
return 0; |
} |
|
/* -------- Socket interface ---------- */ |
static struct sock *__l2cap_get_sock_by_addr(__u16 psm, bdaddr_t *src) |
{ |
struct sock *sk; |
for (sk = l2cap_sk_list.head; sk; sk = sk->next) { |
if (sk->sport == psm && !bacmp(&bluez_pi(sk)->src, src)) |
break; |
} |
return sk; |
} |
|
/* Find socket with psm and source bdaddr. |
* Returns closest match. |
*/ |
static struct sock *__l2cap_get_sock_by_psm(int state, __u16 psm, bdaddr_t *src) |
{ |
struct sock *sk, *sk1 = NULL; |
|
for (sk = l2cap_sk_list.head; sk; sk = sk->next) { |
if (state && sk->state != state) |
continue; |
|
if (l2cap_pi(sk)->psm == psm) { |
/* Exact match. */ |
if (!bacmp(&bluez_pi(sk)->src, src)) |
break; |
|
/* Closest match */ |
if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY)) |
sk1 = sk; |
} |
} |
return sk ? sk : sk1; |
} |
|
/* Find socket with given address (psm, src). |
* Returns locked socket */ |
static inline struct sock *l2cap_get_sock_by_psm(int state, __u16 psm, bdaddr_t *src) |
{ |
struct sock *s; |
read_lock(&l2cap_sk_list.lock); |
s = __l2cap_get_sock_by_psm(state, psm, src); |
if (s) bh_lock_sock(s); |
read_unlock(&l2cap_sk_list.lock); |
return s; |
} |
|
static void l2cap_sock_destruct(struct sock *sk) |
{ |
BT_DBG("sk %p", sk); |
|
skb_queue_purge(&sk->receive_queue); |
skb_queue_purge(&sk->write_queue); |
|
MOD_DEC_USE_COUNT; |
} |
|
static void l2cap_sock_cleanup_listen(struct sock *parent) |
{ |
struct sock *sk; |
|
BT_DBG("parent %p", parent); |
|
/* Close not yet accepted channels */ |
while ((sk = bluez_accept_dequeue(parent, NULL))) |
l2cap_sock_close(sk); |
|
parent->state = BT_CLOSED; |
parent->zapped = 1; |
} |
|
/* Kill socket (only if zapped and orphan) |
* Must be called on unlocked socket. |
*/ |
static void l2cap_sock_kill(struct sock *sk) |
{ |
if (!sk->zapped || sk->socket) |
return; |
|
BT_DBG("sk %p state %d", sk, sk->state); |
|
/* Kill poor orphan */ |
bluez_sock_unlink(&l2cap_sk_list, sk); |
sk->dead = 1; |
sock_put(sk); |
} |
|
/* Close socket. |
*/ |
static void __l2cap_sock_close(struct sock *sk, int reason) |
{ |
BT_DBG("sk %p state %d socket %p", sk, sk->state, sk->socket); |
|
switch (sk->state) { |
case BT_LISTEN: |
l2cap_sock_cleanup_listen(sk); |
break; |
|
case BT_CONNECTED: |
case BT_CONFIG: |
case BT_CONNECT2: |
if (sk->type == SOCK_SEQPACKET) { |
struct l2cap_conn *conn = l2cap_pi(sk)->conn; |
l2cap_disconn_req req; |
|
sk->state = BT_DISCONN; |
l2cap_sock_set_timer(sk, sk->sndtimeo); |
|
req.dcid = __cpu_to_le16(l2cap_pi(sk)->dcid); |
req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); |
l2cap_send_req(conn, L2CAP_DISCONN_REQ, L2CAP_DISCONN_REQ_SIZE, &req); |
} else { |
l2cap_chan_del(sk, reason); |
} |
break; |
|
case BT_CONNECT: |
case BT_DISCONN: |
l2cap_chan_del(sk, reason); |
break; |
|
default: |
sk->zapped = 1; |
break; |
}; |
} |
|
/* Must be called on unlocked socket. */ |
static void l2cap_sock_close(struct sock *sk) |
{ |
l2cap_sock_clear_timer(sk); |
lock_sock(sk); |
__l2cap_sock_close(sk, ECONNRESET); |
release_sock(sk); |
l2cap_sock_kill(sk); |
} |
|
static void l2cap_sock_init(struct sock *sk, struct sock *parent) |
{ |
struct l2cap_pinfo *pi = l2cap_pi(sk); |
|
BT_DBG("sk %p", sk); |
|
if (parent) { |
sk->type = parent->type; |
pi->imtu = l2cap_pi(parent)->imtu; |
pi->omtu = l2cap_pi(parent)->omtu; |
pi->link_mode = l2cap_pi(parent)->link_mode; |
} else { |
pi->imtu = L2CAP_DEFAULT_MTU; |
pi->omtu = 0; |
pi->link_mode = 0; |
} |
|
/* Default config options */ |
pi->conf_mtu = L2CAP_DEFAULT_MTU; |
pi->flush_to = L2CAP_DEFAULT_FLUSH_TO; |
} |
|
static struct sock *l2cap_sock_alloc(struct socket *sock, int proto, int prio) |
{ |
struct sock *sk; |
|
if (!(sk = sk_alloc(PF_BLUETOOTH, prio, 1))) |
return NULL; |
|
bluez_sock_init(sock, sk); |
|
sk->zapped = 0; |
|
sk->destruct = l2cap_sock_destruct; |
sk->sndtimeo = L2CAP_CONN_TIMEOUT; |
|
sk->protocol = proto; |
sk->state = BT_OPEN; |
|
l2cap_sock_init_timer(sk); |
|
bluez_sock_link(&l2cap_sk_list, sk); |
|
MOD_INC_USE_COUNT; |
return sk; |
} |
|
static int l2cap_sock_create(struct socket *sock, int protocol) |
{ |
struct sock *sk; |
|
BT_DBG("sock %p", sock); |
|
sock->state = SS_UNCONNECTED; |
|
if (sock->type != SOCK_SEQPACKET && sock->type != SOCK_DGRAM && sock->type != SOCK_RAW) |
return -ESOCKTNOSUPPORT; |
|
if (sock->type == SOCK_RAW && !capable(CAP_NET_RAW)) |
return -EPERM; |
|
sock->ops = &l2cap_sock_ops; |
|
if (!(sk = l2cap_sock_alloc(sock, protocol, GFP_KERNEL))) |
return -ENOMEM; |
|
l2cap_sock_init(sk, NULL); |
return 0; |
} |
|
static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) |
{ |
struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sk %p, %s %d", sk, batostr(&la->l2_bdaddr), la->l2_psm); |
|
if (!addr || addr->sa_family != AF_BLUETOOTH) |
return -EINVAL; |
|
lock_sock(sk); |
|
if (sk->state != BT_OPEN) { |
err = -EBADFD; |
goto done; |
} |
|
write_lock_bh(&l2cap_sk_list.lock); |
if (la->l2_psm && __l2cap_get_sock_by_addr(la->l2_psm, &la->l2_bdaddr)) { |
err = -EADDRINUSE; |
} else { |
/* Save source address */ |
bacpy(&bluez_pi(sk)->src, &la->l2_bdaddr); |
l2cap_pi(sk)->psm = la->l2_psm; |
sk->sport = la->l2_psm; |
sk->state = BT_BOUND; |
} |
write_unlock_bh(&l2cap_sk_list.lock); |
|
done: |
release_sock(sk); |
return err; |
} |
|
static int l2cap_do_connect(struct sock *sk) |
{ |
bdaddr_t *src = &bluez_pi(sk)->src; |
bdaddr_t *dst = &bluez_pi(sk)->dst; |
struct l2cap_conn *conn; |
struct hci_conn *hcon; |
struct hci_dev *hdev; |
int err = 0; |
|
BT_DBG("%s -> %s psm 0x%2.2x", batostr(src), batostr(dst), l2cap_pi(sk)->psm); |
|
if (!(hdev = hci_get_route(dst, src))) |
return -EHOSTUNREACH; |
|
hci_dev_lock_bh(hdev); |
|
err = -ENOMEM; |
|
hcon = hci_connect(hdev, ACL_LINK, dst); |
if (!hcon) |
goto done; |
|
conn = l2cap_conn_add(hcon, 0); |
if (!conn) { |
hci_conn_put(hcon); |
goto done; |
} |
|
err = 0; |
|
/* Update source addr of the socket */ |
bacpy(src, conn->src); |
|
l2cap_chan_add(conn, sk, NULL); |
|
sk->state = BT_CONNECT; |
l2cap_sock_set_timer(sk, sk->sndtimeo); |
|
if (hcon->state == BT_CONNECTED) { |
if (sk->type == SOCK_SEQPACKET) { |
l2cap_conn_req req; |
req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); |
req.psm = l2cap_pi(sk)->psm; |
l2cap_send_req(conn, L2CAP_CONN_REQ, L2CAP_CONN_REQ_SIZE, &req); |
} else { |
l2cap_sock_clear_timer(sk); |
sk->state = BT_CONNECTED; |
} |
} |
|
done: |
hci_dev_unlock_bh(hdev); |
hci_dev_put(hdev); |
return err; |
} |
|
static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags) |
{ |
struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; |
struct sock *sk = sock->sk; |
int err = 0; |
|
lock_sock(sk); |
|
BT_DBG("sk %p", sk); |
|
if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_l2)) { |
err = -EINVAL; |
goto done; |
} |
|
if (sk->type == SOCK_SEQPACKET && !la->l2_psm) { |
err = -EINVAL; |
goto done; |
} |
|
switch(sk->state) { |
case BT_CONNECT: |
case BT_CONNECT2: |
case BT_CONFIG: |
/* Already connecting */ |
goto wait; |
|
case BT_CONNECTED: |
/* Already connected */ |
goto done; |
|
case BT_OPEN: |
case BT_BOUND: |
/* Can connect */ |
break; |
|
default: |
err = -EBADFD; |
goto done; |
} |
|
/* Set destination address and psm */ |
bacpy(&bluez_pi(sk)->dst, &la->l2_bdaddr); |
l2cap_pi(sk)->psm = la->l2_psm; |
|
if ((err = l2cap_do_connect(sk))) |
goto done; |
|
wait: |
err = bluez_sock_wait_state(sk, BT_CONNECTED, |
sock_sndtimeo(sk, flags & O_NONBLOCK)); |
|
done: |
release_sock(sk); |
return err; |
} |
|
int l2cap_sock_listen(struct socket *sock, int backlog) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sk %p backlog %d", sk, backlog); |
|
lock_sock(sk); |
|
if (sk->state != BT_BOUND || sock->type != SOCK_SEQPACKET) { |
err = -EBADFD; |
goto done; |
} |
|
if (!l2cap_pi(sk)->psm) { |
err = -EINVAL; |
goto done; |
} |
|
sk->max_ack_backlog = backlog; |
sk->ack_backlog = 0; |
sk->state = BT_LISTEN; |
|
done: |
release_sock(sk); |
return err; |
} |
|
int l2cap_sock_accept(struct socket *sock, struct socket *newsock, int flags) |
{ |
DECLARE_WAITQUEUE(wait, current); |
struct sock *sk = sock->sk, *nsk; |
long timeo; |
int err = 0; |
|
lock_sock(sk); |
|
if (sk->state != BT_LISTEN) { |
err = -EBADFD; |
goto done; |
} |
|
timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); |
|
BT_DBG("sk %p timeo %ld", sk, timeo); |
|
/* Wait for an incoming connection. (wake-one). */ |
add_wait_queue_exclusive(sk->sleep, &wait); |
while (!(nsk = bluez_accept_dequeue(sk, newsock))) { |
set_current_state(TASK_INTERRUPTIBLE); |
if (!timeo) { |
err = -EAGAIN; |
break; |
} |
|
release_sock(sk); |
timeo = schedule_timeout(timeo); |
lock_sock(sk); |
|
if (sk->state != BT_LISTEN) { |
err = -EBADFD; |
break; |
} |
|
if (signal_pending(current)) { |
err = sock_intr_errno(timeo); |
break; |
} |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(sk->sleep, &wait); |
|
if (err) |
goto done; |
|
newsock->state = SS_CONNECTED; |
|
BT_DBG("new socket %p", nsk); |
|
done: |
release_sock(sk); |
return err; |
} |
|
static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer) |
{ |
struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; |
struct sock *sk = sock->sk; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
addr->sa_family = AF_BLUETOOTH; |
*len = sizeof(struct sockaddr_l2); |
|
if (peer) |
bacpy(&la->l2_bdaddr, &bluez_pi(sk)->dst); |
else |
bacpy(&la->l2_bdaddr, &bluez_pi(sk)->src); |
|
la->l2_psm = l2cap_pi(sk)->psm; |
return 0; |
} |
|
static int l2cap_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
if (sk->err) |
return sock_error(sk); |
|
if (msg->msg_flags & MSG_OOB) |
return -EOPNOTSUPP; |
|
/* Check outgoing MTU */ |
if (len > l2cap_pi(sk)->omtu) |
return -EINVAL; |
|
lock_sock(sk); |
|
if (sk->state == BT_CONNECTED) |
err = l2cap_chan_send(sk, msg, len); |
else |
err = -ENOTCONN; |
|
release_sock(sk); |
return err; |
} |
|
static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen) |
{ |
struct sock *sk = sock->sk; |
struct l2cap_options opts; |
int err = 0, len; |
__u32 opt; |
|
BT_DBG("sk %p", sk); |
|
lock_sock(sk); |
|
switch (optname) { |
case L2CAP_OPTIONS: |
len = MIN(sizeof(opts), optlen); |
if (copy_from_user((char *)&opts, optval, len)) { |
err = -EFAULT; |
break; |
} |
l2cap_pi(sk)->imtu = opts.imtu; |
l2cap_pi(sk)->omtu = opts.omtu; |
break; |
|
case L2CAP_LM: |
if (get_user(opt, (__u32 *)optval)) { |
err = -EFAULT; |
break; |
} |
|
l2cap_pi(sk)->link_mode = opt; |
break; |
|
default: |
err = -ENOPROTOOPT; |
break; |
} |
|
release_sock(sk); |
return err; |
} |
|
static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen) |
{ |
struct sock *sk = sock->sk; |
struct l2cap_options opts; |
struct l2cap_conninfo cinfo; |
int len, err = 0; |
|
if (get_user(len, optlen)) |
return -EFAULT; |
|
lock_sock(sk); |
|
switch (optname) { |
case L2CAP_OPTIONS: |
opts.imtu = l2cap_pi(sk)->imtu; |
opts.omtu = l2cap_pi(sk)->omtu; |
opts.flush_to = l2cap_pi(sk)->flush_to; |
|
len = MIN(len, sizeof(opts)); |
if (copy_to_user(optval, (char *)&opts, len)) |
err = -EFAULT; |
|
break; |
|
case L2CAP_LM: |
if (put_user(l2cap_pi(sk)->link_mode, (__u32 *)optval)) |
err = -EFAULT; |
break; |
|
case L2CAP_CONNINFO: |
if (sk->state != BT_CONNECTED) { |
err = -ENOTCONN; |
break; |
} |
|
cinfo.hci_handle = l2cap_pi(sk)->conn->hcon->handle; |
|
len = MIN(len, sizeof(cinfo)); |
if (copy_to_user(optval, (char *)&cinfo, len)) |
err = -EFAULT; |
|
break; |
|
default: |
err = -ENOPROTOOPT; |
break; |
} |
|
release_sock(sk); |
return err; |
} |
|
static int l2cap_sock_shutdown(struct socket *sock, int how) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
if (!sk) return 0; |
|
lock_sock(sk); |
if (!sk->shutdown) { |
sk->shutdown = SHUTDOWN_MASK; |
l2cap_sock_clear_timer(sk); |
__l2cap_sock_close(sk, 0); |
|
if (sk->linger) |
err = bluez_sock_wait_state(sk, BT_CLOSED, sk->lingertime); |
} |
release_sock(sk); |
return err; |
} |
|
static int l2cap_sock_release(struct socket *sock) |
{ |
struct sock *sk = sock->sk; |
int err; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
if (!sk) return 0; |
|
err = l2cap_sock_shutdown(sock, 2); |
|
sock_orphan(sk); |
l2cap_sock_kill(sk); |
return err; |
} |
|
/* --------- L2CAP channels --------- */ |
static struct sock * __l2cap_get_chan_by_dcid(struct l2cap_chan_list *l, __u16 cid) |
{ |
struct sock *s; |
for (s = l->head; s; s = l2cap_pi(s)->next_c) { |
if (l2cap_pi(s)->dcid == cid) |
break; |
} |
return s; |
} |
|
static struct sock *__l2cap_get_chan_by_scid(struct l2cap_chan_list *l, __u16 cid) |
{ |
struct sock *s; |
for (s = l->head; s; s = l2cap_pi(s)->next_c) { |
if (l2cap_pi(s)->scid == cid) |
break; |
} |
return s; |
} |
|
/* Find channel with given SCID. |
* Returns locked socket */ |
static inline struct sock *l2cap_get_chan_by_scid(struct l2cap_chan_list *l, __u16 cid) |
{ |
struct sock *s; |
read_lock(&l->lock); |
s = __l2cap_get_chan_by_scid(l, cid); |
if (s) bh_lock_sock(s); |
read_unlock(&l->lock); |
return s; |
} |
|
static __u16 l2cap_alloc_cid(struct l2cap_chan_list *l) |
{ |
__u16 cid = 0x0040; |
|
for (; cid < 0xffff; cid++) { |
if(!__l2cap_get_chan_by_scid(l, cid)) |
return cid; |
} |
|
return 0; |
} |
|
static inline void __l2cap_chan_link(struct l2cap_chan_list *l, struct sock *sk) |
{ |
sock_hold(sk); |
|
if (l->head) |
l2cap_pi(l->head)->prev_c = sk; |
|
l2cap_pi(sk)->next_c = l->head; |
l2cap_pi(sk)->prev_c = NULL; |
l->head = sk; |
} |
|
static inline void l2cap_chan_unlink(struct l2cap_chan_list *l, struct sock *sk) |
{ |
struct sock *next = l2cap_pi(sk)->next_c, *prev = l2cap_pi(sk)->prev_c; |
|
write_lock(&l->lock); |
if (sk == l->head) |
l->head = next; |
|
if (next) |
l2cap_pi(next)->prev_c = prev; |
if (prev) |
l2cap_pi(prev)->next_c = next; |
write_unlock(&l->lock); |
|
__sock_put(sk); |
} |
|
static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent) |
{ |
struct l2cap_chan_list *l = &conn->chan_list; |
|
BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn, l2cap_pi(sk)->psm, l2cap_pi(sk)->dcid); |
|
l2cap_pi(sk)->conn = conn; |
|
if (sk->type == SOCK_SEQPACKET) { |
/* Alloc CID for connection-oriented socket */ |
l2cap_pi(sk)->scid = l2cap_alloc_cid(l); |
} else if (sk->type == SOCK_DGRAM) { |
/* Connectionless socket */ |
l2cap_pi(sk)->scid = 0x0002; |
l2cap_pi(sk)->dcid = 0x0002; |
l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU; |
} else { |
/* Raw socket can send/recv signalling messages only */ |
l2cap_pi(sk)->scid = 0x0001; |
l2cap_pi(sk)->dcid = 0x0001; |
l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU; |
} |
|
__l2cap_chan_link(l, sk); |
|
if (parent) |
bluez_accept_enqueue(parent, sk); |
} |
|
static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent) |
{ |
struct l2cap_chan_list *l = &conn->chan_list; |
write_lock(&l->lock); |
__l2cap_chan_add(conn, sk, parent); |
write_unlock(&l->lock); |
} |
|
/* Delete channel. |
* Must be called on the locked socket. */ |
static void l2cap_chan_del(struct sock *sk, int err) |
{ |
struct l2cap_conn *conn = l2cap_pi(sk)->conn; |
struct sock *parent = bluez_pi(sk)->parent; |
|
l2cap_sock_clear_timer(sk); |
|
BT_DBG("sk %p, conn %p, err %d", sk, conn, err); |
|
if (conn) { |
/* Unlink from channel list */ |
l2cap_chan_unlink(&conn->chan_list, sk); |
l2cap_pi(sk)->conn = NULL; |
hci_conn_put(conn->hcon); |
} |
|
sk->state = BT_CLOSED; |
sk->zapped = 1; |
|
if (err) |
sk->err = err; |
|
if (parent) |
parent->data_ready(parent, 0); |
else |
sk->state_change(sk); |
} |
|
static void l2cap_conn_ready(struct l2cap_conn *conn) |
{ |
struct l2cap_chan_list *l = &conn->chan_list; |
struct sock *sk; |
|
BT_DBG("conn %p", conn); |
|
read_lock(&l->lock); |
|
for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { |
bh_lock_sock(sk); |
|
if (sk->type != SOCK_SEQPACKET) { |
l2cap_sock_clear_timer(sk); |
sk->state = BT_CONNECTED; |
sk->state_change(sk); |
} else if (sk->state == BT_CONNECT) { |
l2cap_conn_req req; |
req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); |
req.psm = l2cap_pi(sk)->psm; |
l2cap_send_req(conn, L2CAP_CONN_REQ, L2CAP_CONN_REQ_SIZE, &req); |
} |
|
bh_unlock_sock(sk); |
} |
|
read_unlock(&l->lock); |
} |
|
/* Notify sockets that we cannot guaranty reliability anymore */ |
static void l2cap_conn_unreliable(struct l2cap_conn *conn, int err) |
{ |
struct l2cap_chan_list *l = &conn->chan_list; |
struct sock *sk; |
|
BT_DBG("conn %p", conn); |
|
read_lock(&l->lock); |
for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { |
if (l2cap_pi(sk)->link_mode & L2CAP_LM_RELIABLE) |
sk->err = err; |
} |
read_unlock(&l->lock); |
} |
|
static void l2cap_chan_ready(struct sock *sk) |
{ |
struct sock *parent = bluez_pi(sk)->parent; |
|
BT_DBG("sk %p, parent %p", sk, parent); |
|
l2cap_pi(sk)->conf_state = 0; |
l2cap_sock_clear_timer(sk); |
|
if (!parent) { |
/* Outgoing channel. |
* Wake up socket sleeping on connect. |
*/ |
sk->state = BT_CONNECTED; |
sk->state_change(sk); |
} else { |
/* Incomming channel. |
* Wake up socket sleeping on accept. |
*/ |
parent->data_ready(parent, 0); |
} |
} |
|
/* Copy frame to all raw sockets on that connection */ |
void l2cap_raw_recv(struct l2cap_conn *conn, struct sk_buff *skb) |
{ |
struct l2cap_chan_list *l = &conn->chan_list; |
struct sk_buff *nskb; |
struct sock * sk; |
|
BT_DBG("conn %p", conn); |
|
read_lock(&l->lock); |
for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { |
if (sk->type != SOCK_RAW) |
continue; |
|
/* Don't send frame to the socket it came from */ |
if (skb->sk == sk) |
continue; |
|
if (!(nskb = skb_clone(skb, GFP_ATOMIC))) |
continue; |
|
if (sock_queue_rcv_skb(sk, nskb)) |
kfree_skb(nskb); |
} |
read_unlock(&l->lock); |
} |
|
static int l2cap_chan_send(struct sock *sk, struct msghdr *msg, int len) |
{ |
struct l2cap_conn *conn = l2cap_pi(sk)->conn; |
struct sk_buff *skb, **frag; |
int err, hlen, count, sent=0; |
l2cap_hdr *lh; |
|
BT_DBG("sk %p len %d", sk, len); |
|
/* First fragment (with L2CAP header) */ |
if (sk->type == SOCK_DGRAM) |
hlen = L2CAP_HDR_SIZE + 2; |
else |
hlen = L2CAP_HDR_SIZE; |
|
count = MIN(conn->mtu - hlen, len); |
|
skb = bluez_skb_send_alloc(sk, hlen + count, |
msg->msg_flags & MSG_DONTWAIT, &err); |
if (!skb) |
return err; |
|
/* Create L2CAP header */ |
lh = (l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); |
lh->cid = __cpu_to_le16(l2cap_pi(sk)->dcid); |
lh->len = __cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE)); |
|
if (sk->type == SOCK_DGRAM) |
put_unaligned(l2cap_pi(sk)->psm, (__u16 *) skb_put(skb, 2)); |
|
if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) { |
err = -EFAULT; |
goto fail; |
} |
|
sent += count; |
len -= count; |
|
/* Continuation fragments (no L2CAP header) */ |
frag = &skb_shinfo(skb)->frag_list; |
while (len) { |
count = MIN(conn->mtu, len); |
|
*frag = bluez_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err); |
if (!*frag) |
goto fail; |
|
if (memcpy_fromiovec(skb_put(*frag, count), msg->msg_iov, count)) { |
err = -EFAULT; |
goto fail; |
} |
|
sent += count; |
len -= count; |
|
frag = &(*frag)->next; |
} |
|
if ((err = hci_send_acl(conn->hcon, skb, 0)) < 0) |
goto fail; |
|
return sent; |
|
fail: |
kfree_skb(skb); |
return err; |
} |
|
/* --------- L2CAP signalling commands --------- */ |
static inline __u8 l2cap_get_ident(struct l2cap_conn *conn) |
{ |
__u8 id; |
|
/* Get next available identificator. |
* 1 - 199 are used by kernel. |
* 200 - 254 are used by utilities like l2ping, etc |
*/ |
|
spin_lock(&conn->lock); |
|
if (++conn->tx_ident > 199) |
conn->tx_ident = 1; |
|
id = conn->tx_ident; |
|
spin_unlock(&conn->lock); |
|
return id; |
} |
|
static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn, |
__u8 code, __u8 ident, __u16 dlen, void *data) |
{ |
struct sk_buff *skb, **frag; |
l2cap_cmd_hdr *cmd; |
l2cap_hdr *lh; |
int len, count; |
|
BT_DBG("conn %p, code 0x%2.2x, ident 0x%2.2x, len %d", conn, code, ident, dlen); |
|
len = L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE + dlen; |
count = MIN(conn->mtu, len); |
|
skb = bluez_skb_alloc(count, GFP_ATOMIC); |
if (!skb) |
return NULL; |
|
lh = (l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); |
lh->len = __cpu_to_le16(L2CAP_CMD_HDR_SIZE + dlen); |
lh->cid = __cpu_to_le16(0x0001); |
|
cmd = (l2cap_cmd_hdr *) skb_put(skb, L2CAP_CMD_HDR_SIZE); |
cmd->code = code; |
cmd->ident = ident; |
cmd->len = __cpu_to_le16(dlen); |
|
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(conn->mtu, len); |
|
*frag = bluez_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 int l2cap_send_req(struct l2cap_conn *conn, __u8 code, __u16 len, void *data) |
{ |
__u8 ident = l2cap_get_ident(conn); |
struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data); |
|
BT_DBG("code 0x%2.2x", code); |
|
if (!skb) |
return -ENOMEM; |
return hci_send_acl(conn->hcon, skb, 0); |
} |
|
static int l2cap_send_rsp(struct l2cap_conn *conn, __u8 ident, __u8 code, __u16 len, void *data) |
{ |
struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data); |
|
BT_DBG("code 0x%2.2x", code); |
|
if (!skb) |
return -ENOMEM; |
return hci_send_acl(conn->hcon, skb, 0); |
} |
|
static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, unsigned long *val) |
{ |
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: |
*val = __le16_to_cpu(*((__u16 *)opt->val)); |
break; |
|
case 4: |
*val = __le32_to_cpu(*((__u32 *)opt->val)); |
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 inline void l2cap_parse_conf_req(struct sock *sk, void *data, int len) |
{ |
int type, hint, olen; |
unsigned long val; |
void *ptr = data; |
|
BT_DBG("sk %p len %d", sk, len); |
|
while (len >= L2CAP_CONF_OPT_SIZE) { |
len -= l2cap_get_conf_opt(&ptr, &type, &olen, &val); |
|
hint = type & 0x80; |
type &= 0x7f; |
|
switch (type) { |
case L2CAP_CONF_MTU: |
l2cap_pi(sk)->conf_mtu = val; |
break; |
|
case L2CAP_CONF_FLUSH_TO: |
l2cap_pi(sk)->flush_to = val; |
break; |
|
case L2CAP_CONF_QOS: |
break; |
|
default: |
if (hint) |
break; |
|
/* FIXME: Reject unknown option */ |
break; |
}; |
} |
} |
|
static void l2cap_add_conf_opt(void **ptr, __u8 type, __u8 len, unsigned long val) |
{ |
register 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: |
*((__u16 *) opt->val) = __cpu_to_le16(val); |
break; |
|
case 4: |
*((__u32 *) opt->val) = __cpu_to_le32(val); |
break; |
|
default: |
memcpy(opt->val, (void *) val, len); |
break; |
}; |
|
*ptr += L2CAP_CONF_OPT_SIZE + len; |
} |
|
static int l2cap_build_conf_req(struct sock *sk, void *data) |
{ |
struct l2cap_pinfo *pi = l2cap_pi(sk); |
l2cap_conf_req *req = (l2cap_conf_req *) data; |
void *ptr = req->data; |
|
BT_DBG("sk %p", sk); |
|
if (pi->imtu != L2CAP_DEFAULT_MTU) |
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu); |
|
/* FIXME. Need actual value of the flush timeout */ |
//if (flush_to != L2CAP_DEFAULT_FLUSH_TO) |
// l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO, 2, pi->flush_to); |
|
req->dcid = __cpu_to_le16(pi->dcid); |
req->flags = __cpu_to_le16(0); |
|
return ptr - data; |
} |
|
static inline int l2cap_conf_output(struct sock *sk, void **ptr) |
{ |
struct l2cap_pinfo *pi = l2cap_pi(sk); |
int result = 0; |
|
/* Configure output options and let the other side know |
* which ones we don't like. |
*/ |
if (pi->conf_mtu < pi->omtu) { |
l2cap_add_conf_opt(ptr, L2CAP_CONF_MTU, 2, pi->omtu); |
result = L2CAP_CONF_UNACCEPT; |
} else { |
pi->omtu = pi->conf_mtu; |
} |
|
BT_DBG("sk %p result %d", sk, result); |
return result; |
} |
|
static int l2cap_build_conf_rsp(struct sock *sk, void *data, int *result) |
{ |
l2cap_conf_rsp *rsp = (l2cap_conf_rsp *) data; |
void *ptr = rsp->data; |
u16 flags = 0; |
|
BT_DBG("sk %p complete %d", sk, result ? 1 : 0); |
|
if (result) |
*result = l2cap_conf_output(sk, &ptr); |
else |
flags |= 0x0001; |
|
rsp->scid = __cpu_to_le16(l2cap_pi(sk)->dcid); |
rsp->result = __cpu_to_le16(result ? *result : 0); |
rsp->flags = __cpu_to_le16(flags); |
|
return ptr - data; |
} |
|
static inline int l2cap_connect_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) |
{ |
struct l2cap_chan_list *list = &conn->chan_list; |
l2cap_conn_req *req = (l2cap_conn_req *) data; |
l2cap_conn_rsp rsp; |
struct sock *sk, *parent; |
int result = 0, status = 0; |
|
__u16 dcid = 0, scid = __le16_to_cpu(req->scid); |
__u16 psm = req->psm; |
|
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) { |
result = L2CAP_CR_BAD_PSM; |
goto sendresp; |
} |
|
result = L2CAP_CR_NO_MEM; |
|
/* Check for backlog size */ |
if (parent->ack_backlog > parent->max_ack_backlog) { |
BT_DBG("backlog full %d", parent->ack_backlog); |
goto response; |
} |
|
sk = l2cap_sock_alloc(NULL, BTPROTO_L2CAP, GFP_ATOMIC); |
if (!sk) |
goto response; |
|
write_lock(&list->lock); |
|
/* Check if we already have channel with that dcid */ |
if (__l2cap_get_chan_by_dcid(list, scid)) { |
write_unlock(&list->lock); |
sk->zapped = 1; |
l2cap_sock_kill(sk); |
goto response; |
} |
|
hci_conn_hold(conn->hcon); |
|
l2cap_sock_init(sk, parent); |
bacpy(&bluez_pi(sk)->src, conn->src); |
bacpy(&bluez_pi(sk)->dst, conn->dst); |
l2cap_pi(sk)->psm = psm; |
l2cap_pi(sk)->dcid = scid; |
|
__l2cap_chan_add(conn, sk, parent); |
dcid = l2cap_pi(sk)->scid; |
|
l2cap_sock_set_timer(sk, sk->sndtimeo); |
|
/* Service level security */ |
result = L2CAP_CR_PEND; |
status = L2CAP_CS_AUTHEN_PEND; |
sk->state = BT_CONNECT2; |
l2cap_pi(sk)->ident = cmd->ident; |
|
if (l2cap_pi(sk)->link_mode & L2CAP_LM_ENCRYPT) { |
if (!hci_conn_encrypt(conn->hcon)) |
goto done; |
} else if (l2cap_pi(sk)->link_mode & L2CAP_LM_AUTH) { |
if (!hci_conn_auth(conn->hcon)) |
goto done; |
} |
|
sk->state = BT_CONFIG; |
result = status = 0; |
|
done: |
write_unlock(&list->lock); |
|
response: |
bh_unlock_sock(parent); |
|
sendresp: |
rsp.scid = __cpu_to_le16(scid); |
rsp.dcid = __cpu_to_le16(dcid); |
rsp.result = __cpu_to_le16(result); |
rsp.status = __cpu_to_le16(status); |
l2cap_send_rsp(conn, cmd->ident, L2CAP_CONN_RSP, L2CAP_CONN_RSP_SIZE, &rsp); |
return 0; |
} |
|
static inline int l2cap_connect_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) |
{ |
l2cap_conn_rsp *rsp = (l2cap_conn_rsp *) data; |
__u16 scid, dcid, result, status; |
struct sock *sk; |
char 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 (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid))) |
return -ENOENT; |
|
switch (result) { |
case L2CAP_CR_SUCCESS: |
sk->state = BT_CONFIG; |
l2cap_pi(sk)->dcid = dcid; |
l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT; |
|
l2cap_send_req(conn, L2CAP_CONF_REQ, l2cap_build_conf_req(sk, req), req); |
break; |
|
case L2CAP_CR_PEND: |
break; |
|
default: |
l2cap_chan_del(sk, ECONNREFUSED); |
break; |
} |
|
bh_unlock_sock(sk); |
return 0; |
} |
|
static inline int l2cap_config_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) |
{ |
l2cap_conf_req * req = (l2cap_conf_req *) data; |
__u16 dcid, flags; |
__u8 rsp[64]; |
struct sock *sk; |
int result; |
|
dcid = __le16_to_cpu(req->dcid); |
flags = __le16_to_cpu(req->flags); |
|
BT_DBG("dcid 0x%4.4x flags 0x%2.2x", dcid, flags); |
|
if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid))) |
return -ENOENT; |
|
l2cap_parse_conf_req(sk, req->data, cmd->len - L2CAP_CONF_REQ_SIZE); |
|
if (flags & 0x0001) { |
/* Incomplete config. Send empty response. */ |
l2cap_send_rsp(conn, cmd->ident, L2CAP_CONF_RSP, l2cap_build_conf_rsp(sk, rsp, NULL), rsp); |
goto unlock; |
} |
|
/* Complete config. */ |
l2cap_send_rsp(conn, cmd->ident, L2CAP_CONF_RSP, l2cap_build_conf_rsp(sk, rsp, &result), rsp); |
|
if (result) |
goto unlock; |
|
/* Output config done */ |
l2cap_pi(sk)->conf_state |= L2CAP_CONF_OUTPUT_DONE; |
|
if (l2cap_pi(sk)->conf_state & L2CAP_CONF_INPUT_DONE) { |
sk->state = BT_CONNECTED; |
l2cap_chan_ready(sk); |
} else if (!(l2cap_pi(sk)->conf_state & L2CAP_CONF_REQ_SENT)) { |
char req[64]; |
l2cap_send_req(conn, L2CAP_CONF_REQ, l2cap_build_conf_req(sk, req), req); |
} |
|
unlock: |
bh_unlock_sock(sk); |
return 0; |
} |
|
static inline int l2cap_config_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) |
{ |
l2cap_conf_rsp *rsp = (l2cap_conf_rsp *)data; |
__u16 scid, flags, result; |
struct sock *sk; |
int err = 0; |
|
scid = __le16_to_cpu(rsp->scid); |
flags = __le16_to_cpu(rsp->flags); |
result = __le16_to_cpu(rsp->result); |
|
BT_DBG("scid 0x%4.4x flags 0x%2.2x result 0x%2.2x", scid, flags, result); |
|
if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid))) |
return -ENOENT; |
|
switch (result) { |
case L2CAP_CONF_SUCCESS: |
break; |
|
case L2CAP_CONF_UNACCEPT: |
if (++l2cap_pi(sk)->conf_retry < L2CAP_CONF_MAX_RETRIES) { |
char req[128]; |
/* |
It does not make sense to adjust L2CAP parameters |
that are currently defined in the spec. We simply |
resend config request that we sent earlier. It is |
stupid :) but it helps qualification testing |
which expects at least some response from us. |
*/ |
l2cap_send_req(conn, L2CAP_CONF_REQ, |
l2cap_build_conf_req(sk, req), req); |
goto done; |
} |
default: |
sk->state = BT_DISCONN; |
sk->err = ECONNRESET; |
l2cap_sock_set_timer(sk, HZ * 5); |
{ |
l2cap_disconn_req req; |
req.dcid = __cpu_to_le16(l2cap_pi(sk)->dcid); |
req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); |
l2cap_send_req(conn, L2CAP_DISCONN_REQ, L2CAP_DISCONN_REQ_SIZE, &req); |
} |
goto done; |
} |
|
if (flags & 0x01) |
goto done; |
|
/* Input config done */ |
l2cap_pi(sk)->conf_state |= L2CAP_CONF_INPUT_DONE; |
|
if (l2cap_pi(sk)->conf_state & L2CAP_CONF_OUTPUT_DONE) { |
sk->state = BT_CONNECTED; |
l2cap_chan_ready(sk); |
} |
|
done: |
bh_unlock_sock(sk); |
return err; |
} |
|
static inline int l2cap_disconnect_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) |
{ |
l2cap_disconn_req *req = (l2cap_disconn_req *) data; |
l2cap_disconn_rsp rsp; |
__u16 dcid, scid; |
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); |
|
if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid))) |
return 0; |
|
rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid); |
rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid); |
l2cap_send_rsp(conn, cmd->ident, L2CAP_DISCONN_RSP, L2CAP_DISCONN_RSP_SIZE, &rsp); |
|
sk->shutdown = SHUTDOWN_MASK; |
|
l2cap_chan_del(sk, ECONNRESET); |
bh_unlock_sock(sk); |
|
l2cap_sock_kill(sk); |
return 0; |
} |
|
static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) |
{ |
l2cap_disconn_rsp *rsp = (l2cap_disconn_rsp *) data; |
__u16 dcid, scid; |
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); |
|
if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid))) |
return 0; |
l2cap_chan_del(sk, 0); |
bh_unlock_sock(sk); |
|
l2cap_sock_kill(sk); |
return 0; |
} |
|
static inline void l2cap_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb) |
{ |
__u8 *data = skb->data; |
int len = skb->len; |
l2cap_cmd_hdr cmd; |
int err = 0; |
|
l2cap_raw_recv(conn, skb); |
|
while (len >= L2CAP_CMD_HDR_SIZE) { |
memcpy(&cmd, data, L2CAP_CMD_HDR_SIZE); |
data += L2CAP_CMD_HDR_SIZE; |
len -= L2CAP_CMD_HDR_SIZE; |
|
cmd.len = __le16_to_cpu(cmd.len); |
|
BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd.code, cmd.len, cmd.ident); |
|
if (cmd.len > len || !cmd.ident) { |
BT_DBG("corrupted command"); |
break; |
} |
|
switch (cmd.code) { |
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, 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_COMMAND_REJ: |
/* FIXME: We should process this */ |
break; |
|
case L2CAP_ECHO_REQ: |
l2cap_send_rsp(conn, cmd.ident, L2CAP_ECHO_RSP, cmd.len, data); |
break; |
|
case L2CAP_ECHO_RSP: |
case L2CAP_INFO_REQ: |
case L2CAP_INFO_RSP: |
break; |
|
default: |
BT_ERR("Unknown signaling command 0x%2.2x", cmd.code); |
err = -EINVAL; |
break; |
}; |
|
if (err) { |
l2cap_cmd_rej rej; |
BT_DBG("error %d", err); |
|
/* FIXME: Map err to a valid reason. */ |
rej.reason = __cpu_to_le16(0); |
l2cap_send_rsp(conn, cmd.ident, L2CAP_COMMAND_REJ, L2CAP_CMD_REJ_SIZE, &rej); |
} |
|
data += cmd.len; |
len -= cmd.len; |
} |
|
kfree_skb(skb); |
} |
|
static inline int l2cap_data_channel(struct l2cap_conn *conn, __u16 cid, struct sk_buff *skb) |
{ |
struct sock *sk; |
|
sk = l2cap_get_chan_by_scid(&conn->chan_list, cid); |
if (!sk) { |
BT_DBG("unknown cid 0x%4.4x", cid); |
goto drop; |
} |
|
BT_DBG("sk %p, len %d", sk, skb->len); |
|
if (sk->state != BT_CONNECTED) |
goto drop; |
|
if (l2cap_pi(sk)->imtu < skb->len) |
goto drop; |
|
/* 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 */ |
|
if (!sock_queue_rcv_skb(sk, skb)) |
goto done; |
|
drop: |
kfree_skb(skb); |
|
done: |
if (sk) bh_unlock_sock(sk); |
return 0; |
} |
|
static inline int l2cap_conless_channel(struct l2cap_conn *conn, __u16 psm, struct sk_buff *skb) |
{ |
struct sock *sk; |
|
sk = l2cap_get_sock_by_psm(0, psm, conn->src); |
if (!sk) |
goto drop; |
|
BT_DBG("sk %p, len %d", sk, skb->len); |
|
if (sk->state != BT_BOUND && sk->state != BT_CONNECTED) |
goto drop; |
|
if (l2cap_pi(sk)->imtu < skb->len) |
goto drop; |
|
if (!sock_queue_rcv_skb(sk, skb)) |
goto done; |
|
drop: |
kfree_skb(skb); |
|
done: |
if (sk) bh_unlock_sock(sk); |
return 0; |
} |
|
static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) |
{ |
l2cap_hdr *lh = (l2cap_hdr *) skb->data; |
__u16 cid, psm, len; |
|
skb_pull(skb, L2CAP_HDR_SIZE); |
cid = __le16_to_cpu(lh->cid); |
len = __le16_to_cpu(lh->len); |
|
BT_DBG("len %d, cid 0x%4.4x", len, cid); |
|
switch (cid) { |
case 0x0001: |
l2cap_sig_channel(conn, skb); |
break; |
|
case 0x0002: |
psm = get_unaligned((__u16 *) skb->data); |
skb_pull(skb, 2); |
l2cap_conless_channel(conn, psm, skb); |
break; |
|
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; |
register struct sock *sk; |
|
if (type != ACL_LINK) |
return 0; |
|
BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr)); |
|
/* Find listening sockets and check their link_mode */ |
read_lock(&l2cap_sk_list.lock); |
for (sk = l2cap_sk_list.head; sk; sk = sk->next) { |
if (sk->state != BT_LISTEN) |
continue; |
|
if (!bacmp(&bluez_pi(sk)->src, &hdev->bdaddr)) { |
lm1 |= (HCI_LM_ACCEPT | l2cap_pi(sk)->link_mode); |
exact++; |
} else if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY)) |
lm2 |= (HCI_LM_ACCEPT | l2cap_pi(sk)->link_mode); |
} |
read_unlock(&l2cap_sk_list.lock); |
|
return exact ? lm1 : lm2; |
} |
|
static int l2cap_connect_cfm(struct hci_conn *hcon, __u8 status) |
{ |
BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status); |
|
if (hcon->type != ACL_LINK) |
return 0; |
|
if (!status) { |
struct l2cap_conn *conn; |
|
conn = l2cap_conn_add(hcon, status); |
if (conn) |
l2cap_conn_ready(conn); |
} else |
l2cap_conn_del(hcon, bterr(status)); |
|
return 0; |
} |
|
static int l2cap_disconn_ind(struct hci_conn *hcon, __u8 reason) |
{ |
BT_DBG("hcon %p reason %d", hcon, reason); |
|
if (hcon->type != ACL_LINK) |
return 0; |
|
l2cap_conn_del(hcon, bterr(reason)); |
return 0; |
} |
|
static int l2cap_auth_cfm(struct hci_conn *hcon, __u8 status) |
{ |
struct l2cap_chan_list *l; |
struct l2cap_conn *conn; |
l2cap_conn_rsp rsp; |
struct sock *sk; |
int result; |
|
if (!(conn = hcon->l2cap_data)) |
return 0; |
l = &conn->chan_list; |
|
BT_DBG("conn %p", conn); |
|
read_lock(&l->lock); |
|
for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { |
bh_lock_sock(sk); |
|
if (sk->state != BT_CONNECT2 || |
(l2cap_pi(sk)->link_mode & L2CAP_LM_ENCRYPT)) { |
bh_unlock_sock(sk); |
continue; |
} |
|
if (!status) { |
sk->state = BT_CONFIG; |
result = 0; |
} else { |
sk->state = BT_DISCONN; |
l2cap_sock_set_timer(sk, HZ/10); |
result = L2CAP_CR_SEC_BLOCK; |
} |
|
rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid); |
rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid); |
rsp.result = __cpu_to_le16(result); |
rsp.status = __cpu_to_le16(0); |
l2cap_send_rsp(conn, l2cap_pi(sk)->ident, L2CAP_CONN_RSP, |
L2CAP_CONN_RSP_SIZE, &rsp); |
|
bh_unlock_sock(sk); |
} |
|
read_unlock(&l->lock); |
return 0; |
} |
|
static int l2cap_encrypt_cfm(struct hci_conn *hcon, __u8 status) |
{ |
struct l2cap_chan_list *l; |
struct l2cap_conn *conn; |
l2cap_conn_rsp rsp; |
struct sock *sk; |
int result; |
|
if (!(conn = hcon->l2cap_data)) |
return 0; |
l = &conn->chan_list; |
|
BT_DBG("conn %p", conn); |
|
read_lock(&l->lock); |
|
for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { |
bh_lock_sock(sk); |
|
if (sk->state != BT_CONNECT2) { |
bh_unlock_sock(sk); |
continue; |
} |
|
if (!status) { |
sk->state = BT_CONFIG; |
result = 0; |
} else { |
sk->state = BT_DISCONN; |
l2cap_sock_set_timer(sk, HZ/10); |
result = L2CAP_CR_SEC_BLOCK; |
} |
|
rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid); |
rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid); |
rsp.result = __cpu_to_le16(result); |
rsp.status = __cpu_to_le16(0); |
l2cap_send_rsp(conn, l2cap_pi(sk)->ident, L2CAP_CONN_RSP, |
L2CAP_CONN_RSP_SIZE, &rsp); |
|
bh_unlock_sock(sk); |
} |
|
read_unlock(&l->lock); |
return 0; |
} |
|
static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, __u16 flags) |
{ |
struct l2cap_conn *conn = hcon->l2cap_data; |
|
if (!conn && !(conn = l2cap_conn_add(hcon, 0))) |
goto drop; |
|
BT_DBG("conn %p len %d flags 0x%x", conn, skb->len, flags); |
|
if (flags & ACL_START) { |
l2cap_hdr *hdr; |
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); |
} |
|
if (skb->len < 2) { |
BT_ERR("Frame is too short (len %d)", skb->len); |
l2cap_conn_unreliable(conn, ECOMM); |
goto drop; |
} |
|
hdr = (l2cap_hdr *) skb->data; |
len = __le16_to_cpu(hdr->len) + L2CAP_HDR_SIZE; |
|
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; |
} |
|
/* Allocate skb for the complete frame including header */ |
conn->rx_skb = bluez_skb_alloc(len, GFP_ATOMIC); |
if (!conn->rx_skb) |
goto drop; |
|
memcpy(skb_put(conn->rx_skb, skb->len), skb->data, skb->len); |
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; |
} |
|
memcpy(skb_put(conn->rx_skb, skb->len), skb->data, skb->len); |
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; |
} |
|
/* ----- Proc fs support ------ */ |
static int l2cap_sock_dump(char *buf, struct bluez_sock_list *list) |
{ |
struct l2cap_pinfo *pi; |
struct sock *sk; |
char *ptr = buf; |
|
read_lock_bh(&list->lock); |
|
for (sk = list->head; sk; sk = sk->next) { |
pi = l2cap_pi(sk); |
ptr += sprintf(ptr, "%s %s %d %d 0x%4.4x 0x%4.4x %d %d 0x%x\n", |
batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst), |
sk->state, pi->psm, pi->scid, pi->dcid, pi->imtu, pi->omtu, |
pi->link_mode); |
} |
|
read_unlock_bh(&list->lock); |
|
ptr += sprintf(ptr, "\n"); |
return ptr - buf; |
} |
|
static int l2cap_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv) |
{ |
char *ptr = buf; |
int len; |
|
BT_DBG("count %d, offset %ld", count, offset); |
|
ptr += l2cap_sock_dump(ptr, &l2cap_sk_list); |
len = ptr - buf; |
|
if (len <= count + offset) |
*eof = 1; |
|
*start = buf + offset; |
len -= offset; |
|
if (len > count) |
len = count; |
if (len < 0) |
len = 0; |
|
return len; |
} |
|
static struct proto_ops l2cap_sock_ops = { |
family: PF_BLUETOOTH, |
release: l2cap_sock_release, |
bind: l2cap_sock_bind, |
connect: l2cap_sock_connect, |
listen: l2cap_sock_listen, |
accept: l2cap_sock_accept, |
getname: l2cap_sock_getname, |
sendmsg: l2cap_sock_sendmsg, |
recvmsg: bluez_sock_recvmsg, |
poll: bluez_sock_poll, |
socketpair: sock_no_socketpair, |
ioctl: sock_no_ioctl, |
shutdown: l2cap_sock_shutdown, |
setsockopt: l2cap_sock_setsockopt, |
getsockopt: l2cap_sock_getsockopt, |
mmap: sock_no_mmap |
}; |
|
static struct net_proto_family l2cap_sock_family_ops = { |
family: PF_BLUETOOTH, |
create: l2cap_sock_create |
}; |
|
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, |
recv_acldata: l2cap_recv_acldata, |
auth_cfm: l2cap_auth_cfm, |
encrypt_cfm: l2cap_encrypt_cfm |
}; |
|
int __init l2cap_init(void) |
{ |
int err; |
|
if ((err = bluez_sock_register(BTPROTO_L2CAP, &l2cap_sock_family_ops))) { |
BT_ERR("Can't register L2CAP socket"); |
return err; |
} |
|
if ((err = hci_register_proto(&l2cap_hci_proto))) { |
BT_ERR("Can't register L2CAP protocol"); |
return err; |
} |
|
create_proc_read_entry("bluetooth/l2cap", 0, 0, l2cap_read_proc, NULL); |
|
BT_INFO("BlueZ L2CAP ver %s Copyright (C) 2000,2001 Qualcomm Inc", VERSION); |
BT_INFO("Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>"); |
return 0; |
} |
|
void l2cap_cleanup(void) |
{ |
remove_proc_entry("bluetooth/l2cap", NULL); |
|
/* Unregister socket and protocol */ |
if (bluez_sock_unregister(BTPROTO_L2CAP)) |
BT_ERR("Can't unregister L2CAP socket"); |
|
if (hci_unregister_proto(&l2cap_hci_proto)) |
BT_ERR("Can't unregister L2CAP protocol"); |
} |
|
void l2cap_load(void) |
{ |
/* Dummy function to trigger automatic L2CAP module loading by |
other modules that use L2CAP sockets but do not use any other |
symbols from it. */ |
return; |
} |
|
EXPORT_SYMBOL(l2cap_load); |
|
module_init(l2cap_init); |
module_exit(l2cap_cleanup); |
|
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>"); |
MODULE_DESCRIPTION("BlueZ L2CAP ver " VERSION); |
MODULE_LICENSE("GPL"); |
/hci_sock.c
0,0 → 1,648
/* |
BlueZ - Bluetooth protocol stack for Linux |
Copyright (C) 2000-2001 Qualcomm Incorporated |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* BlueZ HCI socket layer. |
* |
* $Id: hci_sock.c,v 1.1.1.1 2004-04-15 01:17:06 phoenix Exp $ |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/init.h> |
#include <linux/skbuff.h> |
#include <linux/tqueue.h> |
#include <linux/interrupt.h> |
#include <linux/socket.h> |
#include <linux/ioctl.h> |
#include <net/sock.h> |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
#include <asm/unaligned.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/hci_core.h> |
|
#ifndef HCI_SOCK_DEBUG |
#undef BT_DBG |
#define BT_DBG( A... ) |
#endif |
|
/* ----- HCI socket interface ----- */ |
|
/* Security filter */ |
static struct hci_sec_filter hci_sec_filter = { |
/* Packet types */ |
0x10, |
/* Events */ |
{ 0x1000d9fe, 0x0000300c }, |
/* Commands */ |
{ |
{ 0x0 }, |
/* OGF_LINK_CTL */ |
{ 0xbe000006, 0x00000001, 0x0000, 0x00 }, |
/* OGF_LINK_POLICY */ |
{ 0x00005200, 0x00000000, 0x0000, 0x00 }, |
/* OGF_HOST_CTL */ |
{ 0xaab00200, 0x2b402aaa, 0x0154, 0x00 }, |
/* OGF_INFO_PARAM */ |
{ 0x000002be, 0x00000000, 0x0000, 0x00 }, |
/* OGF_STATUS_PARAM */ |
{ 0x000000ea, 0x00000000, 0x0000, 0x00 } |
} |
}; |
|
static struct bluez_sock_list hci_sk_list = { |
lock: RW_LOCK_UNLOCKED |
}; |
|
/* Send frame to RAW socket */ |
void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
struct sock * sk; |
|
BT_DBG("hdev %p len %d", hdev, skb->len); |
|
read_lock(&hci_sk_list.lock); |
for (sk = hci_sk_list.head; sk; sk = sk->next) { |
struct hci_filter *flt; |
struct sk_buff *nskb; |
|
if (sk->state != BT_BOUND || hci_pi(sk)->hdev != hdev) |
continue; |
|
/* Don't send frame to the socket it came from */ |
if (skb->sk == sk) |
continue; |
|
/* Apply filter */ |
flt = &hci_pi(sk)->filter; |
|
if (!hci_test_bit((skb->pkt_type & HCI_FLT_TYPE_BITS), &flt->type_mask)) |
continue; |
|
if (skb->pkt_type == HCI_EVENT_PKT) { |
register int evt = (*(__u8 *)skb->data & HCI_FLT_EVENT_BITS); |
|
if (!hci_test_bit(evt, &flt->event_mask)) |
continue; |
|
if (flt->opcode && ((evt == EVT_CMD_COMPLETE && |
flt->opcode != *(__u16 *)(skb->data + 3)) || |
(evt == EVT_CMD_STATUS && |
flt->opcode != *(__u16 *)(skb->data + 4)))) |
continue; |
} |
|
if (!(nskb = skb_clone(skb, GFP_ATOMIC))) |
continue; |
|
/* Put type byte before the data */ |
memcpy(skb_push(nskb, 1), &nskb->pkt_type, 1); |
|
if (sock_queue_rcv_skb(sk, nskb)) |
kfree_skb(nskb); |
} |
read_unlock(&hci_sk_list.lock); |
} |
|
static int hci_sock_release(struct socket *sock) |
{ |
struct sock *sk = sock->sk; |
struct hci_dev *hdev = hci_pi(sk)->hdev; |
|
BT_DBG("sock %p sk %p", sock, sk); |
|
if (!sk) |
return 0; |
|
bluez_sock_unlink(&hci_sk_list, sk); |
|
if (hdev) { |
atomic_dec(&hdev->promisc); |
hci_dev_put(hdev); |
} |
|
sock_orphan(sk); |
|
skb_queue_purge(&sk->receive_queue); |
skb_queue_purge(&sk->write_queue); |
|
sock_put(sk); |
|
MOD_DEC_USE_COUNT; |
return 0; |
} |
|
/* Ioctls that require bound socket */ |
static inline int hci_sock_bound_ioctl(struct sock *sk, unsigned int cmd, unsigned long arg) |
{ |
struct hci_dev *hdev = hci_pi(sk)->hdev; |
|
if (!hdev) |
return -EBADFD; |
|
switch (cmd) { |
case HCISETRAW: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
|
if (arg) |
set_bit(HCI_RAW, &hdev->flags); |
else |
clear_bit(HCI_RAW, &hdev->flags); |
|
return 0; |
|
case HCIGETCONNINFO: |
return hci_get_conn_info(hdev, arg); |
|
default: |
if (hdev->ioctl) |
return hdev->ioctl(hdev, cmd, arg); |
return -EINVAL; |
} |
} |
|
static int hci_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) |
{ |
struct sock *sk = sock->sk; |
int err; |
|
BT_DBG("cmd %x arg %lx", cmd, arg); |
|
switch (cmd) { |
case HCIGETDEVLIST: |
return hci_get_dev_list(arg); |
|
case HCIGETDEVINFO: |
return hci_get_dev_info(arg); |
|
case HCIGETCONNLIST: |
return hci_get_conn_list(arg); |
|
case HCIDEVUP: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
return hci_dev_open(arg); |
|
case HCIDEVDOWN: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
return hci_dev_close(arg); |
|
case HCIDEVRESET: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
return hci_dev_reset(arg); |
|
case HCIDEVRESTAT: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
return hci_dev_reset_stat(arg); |
|
case HCISETSCAN: |
case HCISETAUTH: |
case HCISETENCRYPT: |
case HCISETPTYPE: |
case HCISETLINKPOL: |
case HCISETLINKMODE: |
case HCISETACLMTU: |
case HCISETSCOMTU: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
return hci_dev_cmd(cmd, arg); |
|
case HCIINQUIRY: |
return hci_inquiry(arg); |
|
default: |
lock_sock(sk); |
err = hci_sock_bound_ioctl(sk, cmd, arg); |
release_sock(sk); |
return err; |
}; |
} |
|
static int hci_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) |
{ |
struct sockaddr_hci *haddr = (struct sockaddr_hci *) addr; |
struct sock *sk = sock->sk; |
struct hci_dev *hdev = NULL; |
int err = 0; |
|
BT_DBG("sock %p sk %p", sock, sk); |
|
if (!haddr || haddr->hci_family != AF_BLUETOOTH) |
return -EINVAL; |
|
lock_sock(sk); |
|
if (hci_pi(sk)->hdev) { |
err = -EALREADY; |
goto done; |
} |
|
if (haddr->hci_dev != HCI_DEV_NONE) { |
if (!(hdev = hci_dev_get(haddr->hci_dev))) { |
err = -ENODEV; |
goto done; |
} |
|
atomic_inc(&hdev->promisc); |
} |
|
hci_pi(sk)->hdev = hdev; |
sk->state = BT_BOUND; |
|
done: |
release_sock(sk); |
return err; |
} |
|
static int hci_sock_getname(struct socket *sock, struct sockaddr *addr, int *addr_len, int peer) |
{ |
struct sockaddr_hci *haddr = (struct sockaddr_hci *) addr; |
struct sock *sk = sock->sk; |
|
BT_DBG("sock %p sk %p", sock, sk); |
|
lock_sock(sk); |
|
*addr_len = sizeof(*haddr); |
haddr->hci_family = AF_BLUETOOTH; |
haddr->hci_dev = hci_pi(sk)->hdev->id; |
|
release_sock(sk); |
return 0; |
} |
|
static inline void hci_sock_cmsg(struct sock *sk, struct msghdr *msg, struct sk_buff *skb) |
{ |
__u32 mask = hci_pi(sk)->cmsg_mask; |
|
if (mask & HCI_CMSG_DIR) |
put_cmsg(msg, SOL_HCI, HCI_CMSG_DIR, sizeof(int), &bluez_cb(skb)->incomming); |
|
if (mask & HCI_CMSG_TSTAMP) |
put_cmsg(msg, SOL_HCI, HCI_CMSG_TSTAMP, sizeof(skb->stamp), &skb->stamp); |
} |
|
static int hci_sock_recvmsg(struct socket *sock, struct msghdr *msg, int len, int flags, struct scm_cookie *scm) |
{ |
int noblock = flags & MSG_DONTWAIT; |
struct sock *sk = sock->sk; |
struct sk_buff *skb; |
int copied, err; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
if (flags & (MSG_OOB)) |
return -EOPNOTSUPP; |
|
if (sk->state == BT_CLOSED) |
return 0; |
|
if (!(skb = skb_recv_datagram(sk, flags, noblock, &err))) |
return err; |
|
msg->msg_namelen = 0; |
|
copied = skb->len; |
if (len < copied) { |
msg->msg_flags |= MSG_TRUNC; |
copied = len; |
} |
|
skb->h.raw = skb->data; |
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); |
|
hci_sock_cmsg(sk, msg, skb); |
|
skb_free_datagram(sk, skb); |
|
return err ? : copied; |
} |
|
static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, |
struct scm_cookie *scm) |
{ |
struct sock *sk = sock->sk; |
struct hci_dev *hdev; |
struct sk_buff *skb; |
int err; |
|
BT_DBG("sock %p sk %p", sock, sk); |
|
if (msg->msg_flags & MSG_OOB) |
return -EOPNOTSUPP; |
|
if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_NOSIGNAL|MSG_ERRQUEUE)) |
return -EINVAL; |
|
if (len < 4) |
return -EINVAL; |
|
lock_sock(sk); |
|
if (!(hdev = hci_pi(sk)->hdev)) { |
err = -EBADFD; |
goto done; |
} |
|
if (!(skb = bluez_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err))) |
goto done; |
|
if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) { |
err = -EFAULT; |
goto drop; |
} |
|
skb->pkt_type = *((unsigned char *) skb->data); |
skb_pull(skb, 1); |
skb->dev = (void *) hdev; |
|
if (skb->pkt_type == HCI_COMMAND_PKT) { |
u16 opcode = __le16_to_cpu(get_unaligned((u16 *)skb->data)); |
u16 ogf = cmd_opcode_ogf(opcode); |
u16 ocf = cmd_opcode_ocf(opcode); |
|
if (((ogf > HCI_SFLT_MAX_OGF) || |
!hci_test_bit(ocf & HCI_FLT_OCF_BITS, &hci_sec_filter.ocf_mask[ogf])) && |
!capable(CAP_NET_RAW)) { |
err = -EPERM; |
goto drop; |
} |
|
if (test_bit(HCI_RAW, &hdev->flags) || (ogf == OGF_VENDOR_CMD)) { |
skb_queue_tail(&hdev->raw_q, skb); |
hci_sched_tx(hdev); |
} else { |
skb_queue_tail(&hdev->cmd_q, skb); |
hci_sched_cmd(hdev); |
} |
} else { |
if (!capable(CAP_NET_RAW)) { |
err = -EPERM; |
goto drop; |
} |
|
skb_queue_tail(&hdev->raw_q, skb); |
hci_sched_tx(hdev); |
} |
|
err = len; |
|
done: |
release_sock(sk); |
return err; |
|
drop: |
kfree_skb(skb); |
goto done; |
} |
|
int hci_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int len) |
{ |
struct sock *sk = sock->sk; |
struct hci_filter flt = { opcode: 0 }; |
int err = 0, opt = 0; |
|
BT_DBG("sk %p, opt %d", sk, optname); |
|
lock_sock(sk); |
|
switch (optname) { |
case HCI_DATA_DIR: |
if (get_user(opt, (int *)optval)) { |
err = -EFAULT; |
break; |
} |
|
if (opt) |
hci_pi(sk)->cmsg_mask |= HCI_CMSG_DIR; |
else |
hci_pi(sk)->cmsg_mask &= ~HCI_CMSG_DIR; |
break; |
|
case HCI_TIME_STAMP: |
if (get_user(opt, (int *)optval)) { |
err = -EFAULT; |
break; |
} |
|
if (opt) |
hci_pi(sk)->cmsg_mask |= HCI_CMSG_TSTAMP; |
else |
hci_pi(sk)->cmsg_mask &= ~HCI_CMSG_TSTAMP; |
break; |
|
case HCI_FILTER: |
len = MIN(len, sizeof(struct hci_filter)); |
if (copy_from_user(&flt, optval, len)) { |
err = -EFAULT; |
break; |
} |
|
if (!capable(CAP_NET_RAW)) { |
flt.type_mask &= hci_sec_filter.type_mask; |
flt.event_mask[0] &= hci_sec_filter.event_mask[0]; |
flt.event_mask[1] &= hci_sec_filter.event_mask[1]; |
} |
|
memcpy(&hci_pi(sk)->filter, &flt, len); |
break; |
|
default: |
err = -ENOPROTOOPT; |
break; |
}; |
|
release_sock(sk); |
return err; |
} |
|
int hci_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen) |
{ |
struct sock *sk = sock->sk; |
int len, opt; |
|
if (get_user(len, optlen)) |
return -EFAULT; |
|
switch (optname) { |
case HCI_DATA_DIR: |
if (hci_pi(sk)->cmsg_mask & HCI_CMSG_DIR) |
opt = 1; |
else |
opt = 0; |
|
if (put_user(opt, optval)) |
return -EFAULT; |
break; |
|
case HCI_TIME_STAMP: |
if (hci_pi(sk)->cmsg_mask & HCI_CMSG_TSTAMP) |
opt = 1; |
else |
opt = 0; |
|
if (put_user(opt, optval)) |
return -EFAULT; |
break; |
|
case HCI_FILTER: |
len = MIN(len, sizeof(struct hci_filter)); |
if (copy_to_user(optval, &hci_pi(sk)->filter, len)) |
return -EFAULT; |
break; |
|
default: |
return -ENOPROTOOPT; |
break; |
}; |
|
return 0; |
} |
|
struct proto_ops hci_sock_ops = { |
family: PF_BLUETOOTH, |
release: hci_sock_release, |
bind: hci_sock_bind, |
getname: hci_sock_getname, |
sendmsg: hci_sock_sendmsg, |
recvmsg: hci_sock_recvmsg, |
ioctl: hci_sock_ioctl, |
poll: datagram_poll, |
listen: sock_no_listen, |
shutdown: sock_no_shutdown, |
setsockopt: hci_sock_setsockopt, |
getsockopt: hci_sock_getsockopt, |
connect: sock_no_connect, |
socketpair: sock_no_socketpair, |
accept: sock_no_accept, |
mmap: sock_no_mmap |
}; |
|
static int hci_sock_create(struct socket *sock, int protocol) |
{ |
struct sock *sk; |
|
BT_DBG("sock %p", sock); |
|
if (sock->type != SOCK_RAW) |
return -ESOCKTNOSUPPORT; |
|
sock->ops = &hci_sock_ops; |
|
if (!(sk = sk_alloc(PF_BLUETOOTH, GFP_KERNEL, 1))) |
return -ENOMEM; |
|
sock->state = SS_UNCONNECTED; |
sock_init_data(sock, sk); |
|
memset(&sk->protinfo, 0, sizeof(struct hci_pinfo)); |
sk->destruct = NULL; |
sk->protocol = protocol; |
sk->state = BT_OPEN; |
|
bluez_sock_link(&hci_sk_list, sk); |
|
MOD_INC_USE_COUNT; |
return 0; |
} |
|
static int hci_sock_dev_event(struct notifier_block *this, unsigned long event, void *ptr) |
{ |
struct hci_dev *hdev = (struct hci_dev *) ptr; |
evt_si_device sd; |
|
BT_DBG("hdev %s event %ld", hdev->name, event); |
|
/* Send event to sockets */ |
sd.event = event; |
sd.dev_id = hdev->id; |
hci_si_event(NULL, EVT_SI_DEVICE, EVT_SI_DEVICE_SIZE, &sd); |
|
if (event == HCI_DEV_UNREG) { |
struct sock *sk; |
|
/* Detach sockets from device */ |
read_lock(&hci_sk_list.lock); |
for (sk = hci_sk_list.head; sk; sk = sk->next) { |
bh_lock_sock(sk); |
if (hci_pi(sk)->hdev == hdev) { |
hci_pi(sk)->hdev = NULL; |
sk->err = EPIPE; |
sk->state = BT_OPEN; |
sk->state_change(sk); |
|
hci_dev_put(hdev); |
} |
bh_unlock_sock(sk); |
} |
read_unlock(&hci_sk_list.lock); |
} |
|
return NOTIFY_DONE; |
} |
|
struct net_proto_family hci_sock_family_ops = { |
family: PF_BLUETOOTH, |
create: hci_sock_create |
}; |
|
struct notifier_block hci_sock_nblock = { |
notifier_call: hci_sock_dev_event |
}; |
|
int hci_sock_init(void) |
{ |
if (bluez_sock_register(BTPROTO_HCI, &hci_sock_family_ops)) { |
BT_ERR("Can't register HCI socket"); |
return -EPROTO; |
} |
|
hci_register_notifier(&hci_sock_nblock); |
return 0; |
} |
|
int hci_sock_cleanup(void) |
{ |
if (bluez_sock_unregister(BTPROTO_HCI)) |
BT_ERR("Can't unregister HCI socket"); |
|
hci_unregister_notifier(&hci_sock_nblock); |
return 0; |
} |
/cmtp/sock.c
0,0 → 1,208
/* |
CMTP implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org> |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/skbuff.h> |
#include <linux/socket.h> |
#include <linux/ioctl.h> |
#include <linux/file.h> |
#include <net/sock.h> |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
|
#include "cmtp.h" |
|
#ifndef CONFIG_BLUEZ_CMTP_DEBUG |
#undef BT_DBG |
#define BT_DBG(D...) |
#endif |
|
static int cmtp_sock_release(struct socket *sock) |
{ |
struct sock *sk = sock->sk; |
|
BT_DBG("sock %p sk %p", sock, sk); |
|
if (!sk) |
return 0; |
|
sock_orphan(sk); |
sock_put(sk); |
|
MOD_DEC_USE_COUNT; |
return 0; |
} |
|
static int cmtp_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) |
{ |
struct cmtp_connadd_req ca; |
struct cmtp_conndel_req cd; |
struct cmtp_connlist_req cl; |
struct cmtp_conninfo ci; |
struct socket *nsock; |
int err; |
|
BT_DBG("cmd %x arg %lx", cmd, arg); |
|
switch (cmd) { |
case CMTPCONNADD: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
|
if (copy_from_user(&ca, (void *) arg, sizeof(ca))) |
return -EFAULT; |
|
nsock = sockfd_lookup(ca.sock, &err); |
if (!nsock) |
return err; |
|
if (nsock->sk->state != BT_CONNECTED) { |
fput(nsock->file); |
return -EBADFD; |
} |
|
err = cmtp_add_connection(&ca, nsock); |
if (!err) { |
if (copy_to_user((void *) arg, &ca, sizeof(ca))) |
err = -EFAULT; |
} else |
fput(nsock->file); |
|
return err; |
|
case CMTPCONNDEL: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
|
if (copy_from_user(&cd, (void *) arg, sizeof(cd))) |
return -EFAULT; |
|
return cmtp_del_connection(&cd); |
|
case CMTPGETCONNLIST: |
if (copy_from_user(&cl, (void *) arg, sizeof(cl))) |
return -EFAULT; |
|
if (cl.cnum <= 0) |
return -EINVAL; |
|
err = cmtp_get_connlist(&cl); |
if (!err && copy_to_user((void *) arg, &cl, sizeof(cl))) |
return -EFAULT; |
|
return err; |
|
case CMTPGETCONNINFO: |
if (copy_from_user(&ci, (void *) arg, sizeof(ci))) |
return -EFAULT; |
|
err = cmtp_get_conninfo(&ci); |
if (!err && copy_to_user((void *) arg, &ci, sizeof(ci))) |
return -EFAULT; |
|
return err; |
} |
|
return -EINVAL; |
} |
|
static struct proto_ops cmtp_sock_ops = { |
family: PF_BLUETOOTH, |
release: cmtp_sock_release, |
ioctl: cmtp_sock_ioctl, |
bind: sock_no_bind, |
getname: sock_no_getname, |
sendmsg: sock_no_sendmsg, |
recvmsg: sock_no_recvmsg, |
poll: sock_no_poll, |
listen: sock_no_listen, |
shutdown: sock_no_shutdown, |
setsockopt: sock_no_setsockopt, |
getsockopt: sock_no_getsockopt, |
connect: sock_no_connect, |
socketpair: sock_no_socketpair, |
accept: sock_no_accept, |
mmap: sock_no_mmap |
}; |
|
static int cmtp_sock_create(struct socket *sock, int protocol) |
{ |
struct sock *sk; |
|
BT_DBG("sock %p", sock); |
|
if (sock->type != SOCK_RAW) |
return -ESOCKTNOSUPPORT; |
|
sock->ops = &cmtp_sock_ops; |
|
if (!(sk = sk_alloc(PF_BLUETOOTH, GFP_KERNEL, 1))) |
return -ENOMEM; |
|
MOD_INC_USE_COUNT; |
|
sock->state = SS_UNCONNECTED; |
sock_init_data(sock, sk); |
|
sk->destruct = NULL; |
sk->protocol = protocol; |
|
return 0; |
} |
|
static struct net_proto_family cmtp_sock_family_ops = { |
family: PF_BLUETOOTH, |
create: cmtp_sock_create |
}; |
|
int cmtp_init_sockets(void) |
{ |
int err; |
|
if ((err = bluez_sock_register(BTPROTO_CMTP, &cmtp_sock_family_ops))) { |
BT_ERR("Can't register CMTP socket layer (%d)", err); |
return err; |
} |
|
return 0; |
} |
|
void cmtp_cleanup_sockets(void) |
{ |
int err; |
|
if ((err = bluez_sock_unregister(BTPROTO_CMTP))) |
BT_ERR("Can't unregister CMTP socket layer (%d)", err); |
|
return; |
} |
/cmtp/cmtp.h
0,0 → 1,138
/* |
CMTP implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org> |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
#ifndef __CMTP_H |
#define __CMTP_H |
|
#include <linux/types.h> |
#include <net/bluetooth/bluetooth.h> |
|
#define BTNAMSIZ 18 |
|
/* CMTP ioctl defines */ |
#define CMTPCONNADD _IOW('C', 200, int) |
#define CMTPCONNDEL _IOW('C', 201, int) |
#define CMTPGETCONNLIST _IOR('C', 210, int) |
#define CMTPGETCONNINFO _IOR('C', 211, int) |
|
#define CMTP_LOOPBACK 0 |
|
struct cmtp_connadd_req { |
int sock; // Connected socket |
__u32 flags; |
}; |
|
struct cmtp_conndel_req { |
bdaddr_t bdaddr; |
__u32 flags; |
}; |
|
struct cmtp_conninfo { |
bdaddr_t bdaddr; |
__u32 flags; |
__u16 state; |
int num; |
}; |
|
struct cmtp_connlist_req { |
__u32 cnum; |
struct cmtp_conninfo *ci; |
}; |
|
int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock); |
int cmtp_del_connection(struct cmtp_conndel_req *req); |
int cmtp_get_connlist(struct cmtp_connlist_req *req); |
int cmtp_get_conninfo(struct cmtp_conninfo *ci); |
|
/* CMTP session defines */ |
#define CMTP_INTEROP_TIMEOUT (HZ * 5) |
#define CMTP_INITIAL_MSGNUM 0xff00 |
|
struct cmtp_session { |
struct list_head list; |
|
struct socket *sock; |
|
bdaddr_t bdaddr; |
|
unsigned long state; |
unsigned long flags; |
|
uint mtu; |
|
char name[BTNAMSIZ]; |
|
atomic_t terminate; |
|
wait_queue_head_t wait; |
|
int ncontroller; |
int num; |
struct capi_ctr *ctrl; |
|
struct list_head applications; |
|
unsigned long blockids; |
int msgnum; |
|
struct sk_buff_head transmit; |
|
struct sk_buff *reassembly[16]; |
}; |
|
struct cmtp_application { |
struct list_head list; |
|
unsigned long state; |
int err; |
|
__u16 appl; |
__u16 mapping; |
|
__u16 msgnum; |
}; |
|
struct cmtp_scb { |
int id; |
int data; |
}; |
|
int cmtp_attach_device(struct cmtp_session *session); |
void cmtp_detach_device(struct cmtp_session *session); |
|
void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb); |
void cmtp_send_capimsg(struct cmtp_session *session, struct sk_buff *skb); |
|
static inline void cmtp_schedule(struct cmtp_session *session) |
{ |
struct sock *sk = session->sock->sk; |
|
wake_up_interruptible(sk->sleep); |
} |
|
/* CMTP init defines */ |
int cmtp_init_capi(void); |
int cmtp_init_sockets(void); |
void cmtp_cleanup_capi(void); |
void cmtp_cleanup_sockets(void); |
|
#endif /* __CMTP_H */ |
/cmtp/core.c
0,0 → 1,515
/* |
CMTP implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org> |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/skbuff.h> |
#include <linux/socket.h> |
#include <linux/ioctl.h> |
#include <linux/file.h> |
#include <linux/init.h> |
#include <net/sock.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/l2cap.h> |
|
#include "cmtp.h" |
|
#ifndef CONFIG_BLUEZ_CMTP_DEBUG |
#undef BT_DBG |
#define BT_DBG(D...) |
#endif |
|
#define VERSION "1.0" |
|
static DECLARE_RWSEM(cmtp_session_sem); |
static LIST_HEAD(cmtp_session_list); |
|
static struct cmtp_session *__cmtp_get_session(bdaddr_t *bdaddr) |
{ |
struct cmtp_session *session; |
struct list_head *p; |
|
BT_DBG(""); |
|
list_for_each(p, &cmtp_session_list) { |
session = list_entry(p, struct cmtp_session, list); |
if (!bacmp(bdaddr, &session->bdaddr)) |
return session; |
} |
return NULL; |
} |
|
static void __cmtp_link_session(struct cmtp_session *session) |
{ |
MOD_INC_USE_COUNT; |
list_add(&session->list, &cmtp_session_list); |
} |
|
static void __cmtp_unlink_session(struct cmtp_session *session) |
{ |
list_del(&session->list); |
MOD_DEC_USE_COUNT; |
} |
|
static void __cmtp_copy_session(struct cmtp_session *session, struct cmtp_conninfo *ci) |
{ |
bacpy(&ci->bdaddr, &session->bdaddr); |
|
ci->flags = session->flags; |
ci->state = session->state; |
|
ci->num = session->num; |
} |
|
|
static inline int cmtp_alloc_block_id(struct cmtp_session *session) |
{ |
int i, id = -1; |
|
for (i = 0; i < 16; i++) |
if (!test_and_set_bit(i, &session->blockids)) { |
id = i; |
break; |
} |
|
return id; |
} |
|
static inline void cmtp_free_block_id(struct cmtp_session *session, int id) |
{ |
clear_bit(id, &session->blockids); |
} |
|
static inline void cmtp_add_msgpart(struct cmtp_session *session, int id, const unsigned char *buf, int count) |
{ |
struct sk_buff *skb = session->reassembly[id], *nskb; |
int size; |
|
BT_DBG("session %p buf %p count %d", session, buf, count); |
|
size = (skb) ? skb->len + count : count; |
|
if (!(nskb = alloc_skb(size, GFP_ATOMIC))) { |
BT_ERR("Can't allocate memory for CAPI message"); |
return; |
} |
|
if (skb && (skb->len > 0)) |
memcpy(skb_put(nskb, skb->len), skb->data, skb->len); |
|
memcpy(skb_put(nskb, count), buf, count); |
|
session->reassembly[id] = nskb; |
|
if (skb) |
kfree_skb(skb); |
} |
|
static inline int cmtp_recv_frame(struct cmtp_session *session, struct sk_buff *skb) |
{ |
__u8 hdr, hdrlen, id; |
__u16 len; |
|
BT_DBG("session %p skb %p len %d", session, skb, skb->len); |
|
while (skb->len > 0) { |
hdr = skb->data[0]; |
|
switch (hdr & 0xc0) { |
case 0x40: |
hdrlen = 2; |
len = skb->data[1]; |
break; |
case 0x80: |
hdrlen = 3; |
len = skb->data[1] | (skb->data[2] << 8); |
break; |
default: |
hdrlen = 1; |
len = 0; |
break; |
} |
|
id = (hdr & 0x3c) >> 2; |
|
BT_DBG("hdr 0x%02x hdrlen %d len %d id %d", hdr, hdrlen, len, id); |
|
if (hdrlen + len > skb->len) { |
BT_ERR("Wrong size or header information in CMTP frame"); |
break; |
} |
|
if (len == 0) { |
skb_pull(skb, hdrlen); |
continue; |
} |
|
switch (hdr & 0x03) { |
case 0x00: |
cmtp_add_msgpart(session, id, skb->data + hdrlen, len); |
cmtp_recv_capimsg(session, session->reassembly[id]); |
session->reassembly[id] = NULL; |
break; |
case 0x01: |
cmtp_add_msgpart(session, id, skb->data + hdrlen, len); |
break; |
default: |
if (session->reassembly[id] != NULL) |
kfree_skb(session->reassembly[id]); |
session->reassembly[id] = NULL; |
break; |
} |
|
skb_pull(skb, hdrlen + len); |
} |
|
kfree_skb(skb); |
return 0; |
} |
|
static int cmtp_send_frame(struct cmtp_session *session, unsigned char *data, int len) |
{ |
struct socket *sock = session->sock; |
struct iovec iv = { data, len }; |
struct msghdr msg; |
int err; |
|
BT_DBG("session %p data %p len %d", session, data, len); |
|
if (!len) |
return 0; |
|
memset(&msg, 0, sizeof(msg)); |
msg.msg_iovlen = 1; |
msg.msg_iov = &iv; |
|
err = sock->ops->sendmsg(sock, &msg, len, 0); |
return err; |
} |
|
static int cmtp_process_transmit(struct cmtp_session *session) |
{ |
struct sk_buff *skb, *nskb; |
unsigned char *hdr; |
unsigned int size, tail; |
|
BT_DBG("session %p", session); |
|
if (!(nskb = alloc_skb(session->mtu, GFP_ATOMIC))) { |
BT_ERR("Can't allocate memory for new frame"); |
return -ENOMEM; |
} |
|
while ((skb = skb_dequeue(&session->transmit))) { |
struct cmtp_scb *scb = (void *) skb->cb; |
|
if ((tail = (session->mtu - nskb->len)) < 5) { |
cmtp_send_frame(session, nskb->data, nskb->len); |
skb_trim(nskb, 0); |
tail = session->mtu; |
} |
|
size = min_t(uint, ((tail < 258) ? (tail - 2) : (tail - 3)), skb->len); |
|
if ((scb->id < 0) && ((scb->id = cmtp_alloc_block_id(session)) < 0)) { |
skb_queue_head(&session->transmit, skb); |
break; |
} |
|
if (size < 256) { |
hdr = skb_put(nskb, 2); |
hdr[0] = 0x40 |
| ((scb->id << 2) & 0x3c) |
| ((skb->len == size) ? 0x00 : 0x01); |
hdr[1] = size; |
} else { |
hdr = skb_put(nskb, 3); |
hdr[0] = 0x80 |
| ((scb->id << 2) & 0x3c) |
| ((skb->len == size) ? 0x00 : 0x01); |
hdr[1] = size & 0xff; |
hdr[2] = size >> 8; |
} |
|
memcpy(skb_put(nskb, size), skb->data, size); |
skb_pull(skb, size); |
|
if (skb->len > 0) { |
skb_queue_head(&session->transmit, skb); |
} else { |
cmtp_free_block_id(session, scb->id); |
if (scb->data) { |
cmtp_send_frame(session, nskb->data, nskb->len); |
skb_trim(nskb, 0); |
} |
kfree_skb(skb); |
} |
} |
|
cmtp_send_frame(session, nskb->data, nskb->len); |
|
kfree_skb(nskb); |
|
return skb_queue_len(&session->transmit); |
} |
|
static int cmtp_session(void *arg) |
{ |
struct cmtp_session *session = arg; |
struct sock *sk = session->sock->sk; |
struct sk_buff *skb; |
wait_queue_t wait; |
|
BT_DBG("session %p", session); |
|
daemonize(); reparent_to_init(); |
|
sprintf(current->comm, "kcmtpd_ctr_%d", session->num); |
|
sigfillset(¤t->blocked); |
flush_signals(current); |
|
current->nice = -15; |
|
set_fs(KERNEL_DS); |
|
init_waitqueue_entry(&wait, current); |
add_wait_queue(sk->sleep, &wait); |
while (!atomic_read(&session->terminate)) { |
set_current_state(TASK_INTERRUPTIBLE); |
|
if (sk->state != BT_CONNECTED) |
break; |
|
while ((skb = skb_dequeue(&sk->receive_queue))) { |
skb_orphan(skb); |
cmtp_recv_frame(session, skb); |
} |
|
cmtp_process_transmit(session); |
|
schedule(); |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(sk->sleep, &wait); |
|
down_write(&cmtp_session_sem); |
|
if (!(session->flags & (1 << CMTP_LOOPBACK))) |
cmtp_detach_device(session); |
|
fput(session->sock->file); |
|
__cmtp_unlink_session(session); |
|
up_write(&cmtp_session_sem); |
|
kfree(session); |
return 0; |
} |
|
int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock) |
{ |
struct cmtp_session *session, *s; |
bdaddr_t src, dst; |
int i, err; |
|
BT_DBG(""); |
|
baswap(&src, &bluez_pi(sock->sk)->src); |
baswap(&dst, &bluez_pi(sock->sk)->dst); |
|
session = kmalloc(sizeof(struct cmtp_session), GFP_KERNEL); |
if (!session) |
return -ENOMEM; |
memset(session, 0, sizeof(struct cmtp_session)); |
|
down_write(&cmtp_session_sem); |
|
s = __cmtp_get_session(&bluez_pi(sock->sk)->dst); |
if (s && s->state == BT_CONNECTED) { |
err = -EEXIST; |
goto failed; |
} |
|
bacpy(&session->bdaddr, &bluez_pi(sock->sk)->dst); |
|
session->mtu = min_t(uint, l2cap_pi(sock->sk)->omtu, l2cap_pi(sock->sk)->imtu); |
|
BT_DBG("mtu %d", session->mtu); |
|
sprintf(session->name, "%s", batostr(&dst)); |
|
session->sock = sock; |
session->state = BT_CONFIG; |
|
init_waitqueue_head(&session->wait); |
|
session->ctrl = NULL; |
session->msgnum = CMTP_INITIAL_MSGNUM; |
|
INIT_LIST_HEAD(&session->applications); |
|
skb_queue_head_init(&session->transmit); |
|
for (i = 0; i < 16; i++) |
session->reassembly[i] = NULL; |
|
session->flags = req->flags; |
|
__cmtp_link_session(session); |
|
err = kernel_thread(cmtp_session, session, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); |
if (err < 0) |
goto unlink; |
|
if (!(session->flags & (1 << CMTP_LOOPBACK))) { |
err = cmtp_attach_device(session); |
if (err < 0) |
goto detach; |
} |
|
up_write(&cmtp_session_sem); |
return 0; |
|
detach: |
cmtp_detach_device(session); |
|
unlink: |
__cmtp_unlink_session(session); |
|
failed: |
up_write(&cmtp_session_sem); |
kfree(session); |
return err; |
} |
|
int cmtp_del_connection(struct cmtp_conndel_req *req) |
{ |
struct cmtp_session *session; |
int err = 0; |
|
BT_DBG(""); |
|
down_read(&cmtp_session_sem); |
|
session = __cmtp_get_session(&req->bdaddr); |
if (session) { |
/* Flush the transmit queue */ |
skb_queue_purge(&session->transmit); |
|
/* Kill session thread */ |
atomic_inc(&session->terminate); |
cmtp_schedule(session); |
} else |
err = -ENOENT; |
|
up_read(&cmtp_session_sem); |
return err; |
} |
|
int cmtp_get_connlist(struct cmtp_connlist_req *req) |
{ |
struct list_head *p; |
int err = 0, n = 0; |
|
BT_DBG(""); |
|
down_read(&cmtp_session_sem); |
|
list_for_each(p, &cmtp_session_list) { |
struct cmtp_session *session; |
struct cmtp_conninfo ci; |
|
session = list_entry(p, struct cmtp_session, list); |
|
__cmtp_copy_session(session, &ci); |
|
if (copy_to_user(req->ci, &ci, sizeof(ci))) { |
err = -EFAULT; |
break; |
} |
|
if (++n >= req->cnum) |
break; |
|
req->ci++; |
} |
req->cnum = n; |
|
up_read(&cmtp_session_sem); |
return err; |
} |
|
int cmtp_get_conninfo(struct cmtp_conninfo *ci) |
{ |
struct cmtp_session *session; |
int err = 0; |
|
down_read(&cmtp_session_sem); |
|
session = __cmtp_get_session(&ci->bdaddr); |
if (session) |
__cmtp_copy_session(session, ci); |
else |
err = -ENOENT; |
|
up_read(&cmtp_session_sem); |
return err; |
} |
|
|
int __init init_cmtp(void) |
{ |
l2cap_load(); |
|
cmtp_init_capi(); |
cmtp_init_sockets(); |
|
BT_INFO("BlueZ CMTP ver %s", VERSION); |
BT_INFO("Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org>"); |
|
return 0; |
} |
|
void __exit exit_cmtp(void) |
{ |
cmtp_cleanup_sockets(); |
cmtp_cleanup_capi(); |
} |
|
module_init(init_cmtp); |
module_exit(exit_cmtp); |
|
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); |
MODULE_DESCRIPTION("BlueZ CMTP ver " VERSION); |
MODULE_LICENSE("GPL"); |
/cmtp/Config.in
0,0 → 1,7
# |
# Bluetooth CMTP layer configuration |
# |
|
if [ "$CONFIG_ISDN" = "y" -o "$CONFIG_ISDN" = "m" ]; then |
dep_tristate 'CMTP protocol support' CONFIG_BLUEZ_CMTP $CONFIG_ISDN_CAPI $CONFIG_BLUEZ_L2CAP |
fi |
/cmtp/Makefile
0,0 → 1,10
# |
# Makefile for the Linux Bluetooth CMTP layer |
# |
|
O_TARGET := cmtp.o |
|
obj-y := core.o sock.o capi.o |
obj-m += $(O_TARGET) |
|
include $(TOPDIR)/Rules.make |
/cmtp/capi.c
0,0 → 1,707
/* |
CMTP implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org> |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/skbuff.h> |
#include <linux/socket.h> |
#include <linux/ioctl.h> |
#include <linux/file.h> |
#include <net/sock.h> |
|
#include <linux/capi.h> |
|
#include "../drivers/isdn/avmb1/capilli.h" |
#include "../drivers/isdn/avmb1/capicmd.h" |
#include "../drivers/isdn/avmb1/capiutil.h" |
|
#include "cmtp.h" |
|
#ifndef CONFIG_BLUEZ_CMTP_DEBUG |
#undef BT_DBG |
#define BT_DBG(D...) |
#endif |
|
#define REVISION "1.0" |
|
#define CAPI_INTEROPERABILITY 0x20 |
|
#define CAPI_INTEROPERABILITY_REQ CAPICMD(CAPI_INTEROPERABILITY, CAPI_REQ) |
#define CAPI_INTEROPERABILITY_CONF CAPICMD(CAPI_INTEROPERABILITY, CAPI_CONF) |
#define CAPI_INTEROPERABILITY_IND CAPICMD(CAPI_INTEROPERABILITY, CAPI_IND) |
#define CAPI_INTEROPERABILITY_RESP CAPICMD(CAPI_INTEROPERABILITY, CAPI_RESP) |
|
#define CAPI_INTEROPERABILITY_REQ_LEN (CAPI_MSG_BASELEN + 2) |
#define CAPI_INTEROPERABILITY_CONF_LEN (CAPI_MSG_BASELEN + 4) |
#define CAPI_INTEROPERABILITY_IND_LEN (CAPI_MSG_BASELEN + 2) |
#define CAPI_INTEROPERABILITY_RESP_LEN (CAPI_MSG_BASELEN + 2) |
|
#define CAPI_FUNCTION_REGISTER 0 |
#define CAPI_FUNCTION_RELEASE 1 |
#define CAPI_FUNCTION_GET_PROFILE 2 |
#define CAPI_FUNCTION_GET_MANUFACTURER 3 |
#define CAPI_FUNCTION_GET_VERSION 4 |
#define CAPI_FUNCTION_GET_SERIAL_NUMBER 5 |
#define CAPI_FUNCTION_MANUFACTURER 6 |
#define CAPI_FUNCTION_LOOPBACK 7 |
|
static struct capi_driver_interface *di; |
|
|
#define CMTP_MSGNUM 1 |
#define CMTP_APPLID 2 |
#define CMTP_MAPPING 3 |
|
static struct cmtp_application *cmtp_application_add(struct cmtp_session *session, __u16 appl) |
{ |
struct cmtp_application *app = kmalloc(sizeof(*app), GFP_KERNEL); |
|
BT_DBG("session %p application %p appl %d", session, app, appl); |
|
if (!app) |
return NULL; |
|
memset(app, 0, sizeof(*app)); |
|
app->state = BT_OPEN; |
app->appl = appl; |
|
list_add_tail(&app->list, &session->applications); |
|
return app; |
} |
|
static void cmtp_application_del(struct cmtp_session *session, struct cmtp_application *app) |
{ |
BT_DBG("session %p application %p", session, app); |
|
if (app) { |
list_del(&app->list); |
kfree(app); |
} |
} |
|
static struct cmtp_application *cmtp_application_get(struct cmtp_session *session, int pattern, __u16 value) |
{ |
struct cmtp_application *app; |
struct list_head *p, *n; |
|
list_for_each_safe(p, n, &session->applications) { |
app = list_entry(p, struct cmtp_application, list); |
switch (pattern) { |
case CMTP_MSGNUM: |
if (app->msgnum == value) |
return app; |
break; |
case CMTP_APPLID: |
if (app->appl == value) |
return app; |
break; |
case CMTP_MAPPING: |
if (app->mapping == value) |
return app; |
break; |
} |
} |
|
return NULL; |
} |
|
static int cmtp_msgnum_get(struct cmtp_session *session) |
{ |
session->msgnum++; |
|
if ((session->msgnum & 0xff) > 200) |
session->msgnum = CMTP_INITIAL_MSGNUM + 1; |
|
return session->msgnum; |
} |
|
|
static void cmtp_send_interopmsg(struct cmtp_session *session, |
__u8 subcmd, __u16 appl, __u16 msgnum, |
__u16 function, unsigned char *buf, int len) |
{ |
struct sk_buff *skb; |
unsigned char *s; |
|
BT_DBG("session %p subcmd 0x%02x appl %d msgnum %d", session, subcmd, appl, msgnum); |
|
if (!(skb = alloc_skb(CAPI_MSG_BASELEN + 6 + len, GFP_ATOMIC))) { |
BT_ERR("Can't allocate memory for interoperability packet"); |
return; |
} |
|
s = skb_put(skb, CAPI_MSG_BASELEN + 6 + len); |
|
capimsg_setu16(s, 0, CAPI_MSG_BASELEN + 6 + len); |
capimsg_setu16(s, 2, appl); |
capimsg_setu8 (s, 4, CAPI_INTEROPERABILITY); |
capimsg_setu8 (s, 5, subcmd); |
capimsg_setu16(s, 6, msgnum); |
|
/* Interoperability selector (Bluetooth Device Management) */ |
capimsg_setu16(s, 8, 0x0001); |
|
capimsg_setu8 (s, 10, 3 + len); |
capimsg_setu16(s, 11, function); |
capimsg_setu8 (s, 13, len); |
|
if (len > 0) |
memcpy(s + 14, buf, len); |
|
cmtp_send_capimsg(session, skb); |
} |
|
static void cmtp_recv_interopmsg(struct cmtp_session *session, struct sk_buff *skb) |
{ |
struct capi_ctr *ctrl = session->ctrl; |
struct cmtp_application *application; |
__u16 appl, msgnum, func, info; |
__u32 controller; |
|
BT_DBG("session %p skb %p len %d", session, skb, skb->len); |
|
switch (CAPIMSG_SUBCOMMAND(skb->data)) { |
case CAPI_CONF: |
func = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 5); |
info = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 8); |
|
switch (func) { |
case CAPI_FUNCTION_REGISTER: |
msgnum = CAPIMSG_MSGID(skb->data); |
|
application = cmtp_application_get(session, CMTP_MSGNUM, msgnum); |
if (application) { |
application->state = BT_CONNECTED; |
application->msgnum = 0; |
application->mapping = CAPIMSG_APPID(skb->data); |
wake_up_interruptible(&session->wait); |
} |
|
break; |
|
case CAPI_FUNCTION_RELEASE: |
appl = CAPIMSG_APPID(skb->data); |
|
application = cmtp_application_get(session, CMTP_MAPPING, appl); |
if (application) { |
application->state = BT_CLOSED; |
application->msgnum = 0; |
wake_up_interruptible(&session->wait); |
} |
|
break; |
|
case CAPI_FUNCTION_GET_PROFILE: |
controller = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 11); |
msgnum = CAPIMSG_MSGID(skb->data); |
|
if (!info && (msgnum == CMTP_INITIAL_MSGNUM)) { |
session->ncontroller = controller; |
wake_up_interruptible(&session->wait); |
break; |
} |
|
if (!info && ctrl) { |
memcpy(&ctrl->profile, |
skb->data + CAPI_MSG_BASELEN + 11, |
sizeof(capi_profile)); |
session->state = BT_CONNECTED; |
ctrl->ready(ctrl); |
} |
|
break; |
|
case CAPI_FUNCTION_GET_MANUFACTURER: |
controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 10); |
|
if (!info && ctrl) { |
strncpy(ctrl->manu, |
skb->data + CAPI_MSG_BASELEN + 15, |
skb->data[CAPI_MSG_BASELEN + 14]); |
} |
|
break; |
|
case CAPI_FUNCTION_GET_VERSION: |
controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 12); |
|
if (!info && ctrl) { |
ctrl->version.majorversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 16); |
ctrl->version.minorversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 20); |
ctrl->version.majormanuversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 24); |
ctrl->version.minormanuversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 28); |
} |
|
break; |
|
case CAPI_FUNCTION_GET_SERIAL_NUMBER: |
controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 12); |
|
if (!info && ctrl) { |
memset(ctrl->serial, 0, CAPI_SERIAL_LEN); |
strncpy(ctrl->serial, |
skb->data + CAPI_MSG_BASELEN + 17, |
skb->data[CAPI_MSG_BASELEN + 16]); |
} |
|
break; |
} |
|
break; |
|
case CAPI_IND: |
func = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 3); |
|
if (func == CAPI_FUNCTION_LOOPBACK) { |
appl = CAPIMSG_APPID(skb->data); |
msgnum = CAPIMSG_MSGID(skb->data); |
cmtp_send_interopmsg(session, CAPI_RESP, appl, msgnum, func, |
skb->data + CAPI_MSG_BASELEN + 6, |
skb->data[CAPI_MSG_BASELEN + 5]); |
} |
|
break; |
} |
|
kfree_skb(skb); |
} |
|
void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb) |
{ |
struct capi_ctr *ctrl = session->ctrl; |
struct cmtp_application *application; |
__u16 cmd, appl, info; |
__u32 ncci, contr; |
|
BT_DBG("session %p skb %p len %d", session, skb, skb->len); |
|
if (CAPIMSG_COMMAND(skb->data) == CAPI_INTEROPERABILITY) { |
cmtp_recv_interopmsg(session, skb); |
return; |
} |
|
if (session->flags & (1 << CMTP_LOOPBACK)) { |
kfree_skb(skb); |
return; |
} |
|
cmd = CAPICMD(CAPIMSG_COMMAND(skb->data), CAPIMSG_SUBCOMMAND(skb->data)); |
appl = CAPIMSG_APPID(skb->data); |
contr = CAPIMSG_CONTROL(skb->data); |
|
application = cmtp_application_get(session, CMTP_MAPPING, appl); |
if (application) { |
appl = application->appl; |
CAPIMSG_SETAPPID(skb->data, appl); |
} else { |
BT_ERR("Can't find application with id %d", appl); |
kfree_skb(skb); |
return; |
} |
|
if ((contr & 0x7f) == 0x01) { |
contr = (contr & 0xffffff80) | session->num; |
CAPIMSG_SETCONTROL(skb->data, contr); |
} |
|
if (!ctrl) { |
BT_ERR("Can't find controller %d for message", session->num); |
kfree_skb(skb); |
return; |
} |
|
switch (cmd) { |
case CAPI_CONNECT_B3_CONF: |
ncci = CAPIMSG_NCCI(skb->data); |
info = CAPIMSG_U16(skb->data, 12); |
|
BT_DBG("CONNECT_B3_CONF ncci 0x%02x info 0x%02x", ncci, info); |
|
if (info == 0) |
ctrl->new_ncci(ctrl, appl, ncci, 8); |
|
ctrl->handle_capimsg(ctrl, appl, skb); |
break; |
|
case CAPI_CONNECT_B3_IND: |
ncci = CAPIMSG_NCCI(skb->data); |
|
BT_DBG("CONNECT_B3_IND ncci 0x%02x", ncci); |
|
ctrl->new_ncci(ctrl, appl, ncci, 8); |
ctrl->handle_capimsg(ctrl, appl, skb); |
break; |
|
case CAPI_DISCONNECT_B3_IND: |
ncci = CAPIMSG_NCCI(skb->data); |
|
BT_DBG("DISCONNECT_B3_IND ncci 0x%02x", ncci); |
|
if (ncci == 0xffffffff) |
BT_ERR("DISCONNECT_B3_IND with ncci 0xffffffff"); |
|
ctrl->handle_capimsg(ctrl, appl, skb); |
ctrl->free_ncci(ctrl, appl, ncci); |
break; |
|
default: |
ctrl->handle_capimsg(ctrl, appl, skb); |
break; |
} |
} |
|
void cmtp_send_capimsg(struct cmtp_session *session, struct sk_buff *skb) |
{ |
struct cmtp_scb *scb = (void *) skb->cb; |
|
BT_DBG("session %p skb %p len %d", session, skb, skb->len); |
|
scb->id = -1; |
scb->data = (CAPIMSG_COMMAND(skb->data) == CAPI_DATA_B3); |
|
skb_queue_tail(&session->transmit, skb); |
|
cmtp_schedule(session); |
} |
|
|
static int cmtp_load_firmware(struct capi_ctr *ctrl, capiloaddata *data) |
{ |
BT_DBG("ctrl %p data %p", ctrl, data); |
|
return -EIO; |
} |
|
static void cmtp_reset_ctr(struct capi_ctr *ctrl) |
{ |
BT_DBG("ctrl %p", ctrl); |
|
ctrl->reseted(ctrl); |
} |
|
static void cmtp_remove_ctr(struct capi_ctr *ctrl) |
{ |
struct cmtp_session *session = ctrl->driverdata; |
|
BT_DBG("ctrl %p", ctrl); |
|
ctrl->suspend_output(ctrl); |
|
atomic_inc(&session->terminate); |
cmtp_schedule(session); |
} |
|
static void cmtp_register_appl(struct capi_ctr *ctrl, __u16 appl, capi_register_params *rp) |
{ |
DECLARE_WAITQUEUE(wait, current); |
struct cmtp_session *session = ctrl->driverdata; |
struct cmtp_application *application; |
unsigned long timeo = CMTP_INTEROP_TIMEOUT; |
unsigned char buf[8]; |
int err = 0, nconn, want = rp->level3cnt; |
|
BT_DBG("ctrl %p appl %d level3cnt %d datablkcnt %d datablklen %d", |
ctrl, appl, rp->level3cnt, rp->datablkcnt, rp->datablklen); |
|
application = cmtp_application_add(session, appl); |
if (!application) { |
BT_ERR("Can't allocate memory for new application"); |
ctrl->appl_released(ctrl, appl); |
return; |
} |
|
if (want < 0) |
nconn = ctrl->profile.nbchannel * -want; |
else |
nconn = want; |
|
if (nconn == 0) |
nconn = ctrl->profile.nbchannel; |
|
capimsg_setu16(buf, 0, nconn); |
capimsg_setu16(buf, 2, rp->datablkcnt); |
capimsg_setu16(buf, 4, rp->datablklen); |
|
application->state = BT_CONFIG; |
application->msgnum = cmtp_msgnum_get(session); |
|
cmtp_send_interopmsg(session, CAPI_REQ, 0x0000, application->msgnum, |
CAPI_FUNCTION_REGISTER, buf, 6); |
|
add_wait_queue(&session->wait, &wait); |
while (1) { |
set_current_state(TASK_INTERRUPTIBLE); |
|
if (!timeo) { |
err = -EAGAIN; |
break; |
} |
|
if (application->state == BT_CLOSED) { |
err = -application->err; |
break; |
} |
|
if (application->state == BT_CONNECTED) |
break; |
|
if (signal_pending(current)) { |
err = -EINTR; |
break; |
} |
|
timeo = schedule_timeout(timeo); |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(&session->wait, &wait); |
|
if (err) { |
ctrl->appl_released(ctrl, appl); |
cmtp_application_del(session, application); |
return; |
} |
|
ctrl->appl_registered(ctrl, appl); |
} |
|
static void cmtp_release_appl(struct capi_ctr *ctrl, __u16 appl) |
{ |
DECLARE_WAITQUEUE(wait, current); |
struct cmtp_session *session = ctrl->driverdata; |
struct cmtp_application *application; |
unsigned long timeo = CMTP_INTEROP_TIMEOUT; |
|
BT_DBG("ctrl %p appl %d", ctrl, appl); |
|
application = cmtp_application_get(session, CMTP_APPLID, appl); |
if (!application) { |
BT_ERR("Can't find application"); |
return; |
} |
|
application->msgnum = cmtp_msgnum_get(session); |
|
cmtp_send_interopmsg(session, CAPI_REQ, application->mapping, application->msgnum, |
CAPI_FUNCTION_RELEASE, NULL, 0); |
|
add_wait_queue(&session->wait, &wait); |
while (timeo) { |
set_current_state(TASK_INTERRUPTIBLE); |
|
if (application->state == BT_CLOSED) |
break; |
|
if (signal_pending(current)) |
break; |
|
timeo = schedule_timeout(timeo); |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(&session->wait, &wait); |
|
cmtp_application_del(session, application); |
ctrl->appl_released(ctrl, appl); |
} |
|
static void cmtp_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) |
{ |
struct cmtp_session *session = ctrl->driverdata; |
struct cmtp_application *application; |
__u16 appl; |
__u32 contr; |
|
BT_DBG("ctrl %p skb %p", ctrl, skb); |
|
appl = CAPIMSG_APPID(skb->data); |
contr = CAPIMSG_CONTROL(skb->data); |
|
application = cmtp_application_get(session, CMTP_APPLID, appl); |
if ((!application) || (application->state != BT_CONNECTED)) { |
BT_ERR("Can't find application with id %d", appl); |
kfree_skb(skb); |
return; |
} |
|
CAPIMSG_SETAPPID(skb->data, application->mapping); |
|
if ((contr & 0x7f) == session->num) { |
contr = (contr & 0xffffff80) | 0x01; |
CAPIMSG_SETCONTROL(skb->data, contr); |
} |
|
cmtp_send_capimsg(session, skb); |
} |
|
static char *cmtp_procinfo(struct capi_ctr *ctrl) |
{ |
return "CAPI Message Transport Protocol"; |
} |
|
static int cmtp_ctr_read_proc(char *page, char **start, off_t off, int count, int *eof, struct capi_ctr *ctrl) |
{ |
struct cmtp_session *session = ctrl->driverdata; |
struct cmtp_application *app; |
struct list_head *p, *n; |
int len = 0; |
|
len += sprintf(page + len, "%s (Revision %s)\n\n", cmtp_procinfo(ctrl), REVISION); |
len += sprintf(page + len, "addr %s\n", session->name); |
len += sprintf(page + len, "ctrl %d\n", session->num); |
|
list_for_each_safe(p, n, &session->applications) { |
app = list_entry(p, struct cmtp_application, list); |
len += sprintf(page + len, "appl %d -> %d\n", app->appl, app->mapping); |
} |
|
if (off + count >= len) |
*eof = 1; |
|
if (len < off) |
return 0; |
|
*start = page + off; |
|
return ((count < len - off) ? count : len - off); |
} |
|
static struct capi_driver cmtp_driver = { |
name: "cmtp", |
revision: REVISION, |
load_firmware: cmtp_load_firmware, |
reset_ctr: cmtp_reset_ctr, |
remove_ctr: cmtp_remove_ctr, |
register_appl: cmtp_register_appl, |
release_appl: cmtp_release_appl, |
send_message: cmtp_send_message, |
procinfo: cmtp_procinfo, |
ctr_read_proc: cmtp_ctr_read_proc, |
|
driver_read_proc: 0, |
add_card: 0, |
}; |
|
|
int cmtp_attach_device(struct cmtp_session *session) |
{ |
DECLARE_WAITQUEUE(wait, current); |
unsigned long timeo = CMTP_INTEROP_TIMEOUT; |
unsigned char buf[4]; |
|
BT_DBG("session %p", session); |
|
capimsg_setu32(buf, 0, 0); |
|
cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, CMTP_INITIAL_MSGNUM, |
CAPI_FUNCTION_GET_PROFILE, buf, 4); |
|
add_wait_queue(&session->wait, &wait); |
while (timeo) { |
set_current_state(TASK_INTERRUPTIBLE); |
|
if (session->ncontroller) |
break; |
|
if (signal_pending(current)) |
break; |
|
timeo = schedule_timeout(timeo); |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(&session->wait, &wait); |
|
BT_INFO("Found %d CAPI controller(s) on device %s", session->ncontroller, session->name); |
|
if (!timeo) |
return -ETIMEDOUT; |
|
if (!session->ncontroller) |
return -ENODEV; |
|
|
if (session->ncontroller > 1) |
BT_INFO("Setting up only CAPI controller 1"); |
|
if (!(session->ctrl = di->attach_ctr(&cmtp_driver, session->name, session))) { |
BT_ERR("Can't attach new controller"); |
return -EBUSY; |
} |
|
session->num = session->ctrl->cnr; |
|
BT_DBG("session %p ctrl %p num %d", session, session->ctrl, session->num); |
|
capimsg_setu32(buf, 0, 1); |
|
cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), |
CAPI_FUNCTION_GET_MANUFACTURER, buf, 4); |
|
cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), |
CAPI_FUNCTION_GET_VERSION, buf, 4); |
|
cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), |
CAPI_FUNCTION_GET_SERIAL_NUMBER, buf, 4); |
|
cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), |
CAPI_FUNCTION_GET_PROFILE, buf, 4); |
|
return 0; |
} |
|
void cmtp_detach_device(struct cmtp_session *session) |
{ |
struct capi_ctr *ctrl = session->ctrl; |
|
BT_DBG("session %p ctrl %p", session, ctrl); |
|
if (!ctrl) |
return; |
|
ctrl->reseted(ctrl); |
|
di->detach_ctr(ctrl); |
} |
|
int cmtp_init_capi(void) |
{ |
if (!(di = attach_capi_driver(&cmtp_driver))) { |
BT_ERR("Can't attach CAPI driver"); |
return -EIO; |
} |
|
return 0; |
} |
|
void cmtp_cleanup_capi(void) |
{ |
detach_capi_driver(&cmtp_driver); |
} |
/rfcomm/sock.c
0,0 → 1,847
/* |
RFCOMM implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com> |
Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org> |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* RFCOMM sockets. |
* |
* $Id: sock.c,v 1.1.1.1 2004-04-15 01:17:13 phoenix Exp $ |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/init.h> |
#include <linux/skbuff.h> |
#include <linux/interrupt.h> |
#include <linux/socket.h> |
#include <linux/skbuff.h> |
#include <linux/list.h> |
#include <net/sock.h> |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/rfcomm.h> |
|
#ifndef CONFIG_BLUEZ_RFCOMM_DEBUG |
#undef BT_DBG |
#define BT_DBG(D...) |
#endif |
|
static struct proto_ops rfcomm_sock_ops; |
|
static struct bluez_sock_list rfcomm_sk_list = { |
lock: RW_LOCK_UNLOCKED |
}; |
|
static void rfcomm_sock_close(struct sock *sk); |
static void rfcomm_sock_kill(struct sock *sk); |
|
/* ---- DLC callbacks ---- |
* |
* called under rfcomm_dlc_lock() |
*/ |
static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb) |
{ |
struct sock *sk = d->owner; |
if (!sk) |
return; |
|
atomic_add(skb->len, &sk->rmem_alloc); |
skb_queue_tail(&sk->receive_queue, skb); |
sk->data_ready(sk, skb->len); |
|
if (atomic_read(&sk->rmem_alloc) >= sk->rcvbuf) |
rfcomm_dlc_throttle(d); |
} |
|
static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err) |
{ |
struct sock *sk = d->owner, *parent; |
if (!sk) |
return; |
|
BT_DBG("dlc %p state %ld err %d", d, d->state, err); |
|
bh_lock_sock(sk); |
|
if (err) |
sk->err = err; |
sk->state = d->state; |
|
parent = bluez_pi(sk)->parent; |
if (!parent) { |
if (d->state == BT_CONNECTED) |
rfcomm_session_getaddr(d->session, &bluez_pi(sk)->src, NULL); |
sk->state_change(sk); |
} else |
parent->data_ready(parent, 0); |
|
bh_unlock_sock(sk); |
} |
|
/* ---- Socket functions ---- */ |
static struct sock *__rfcomm_get_sock_by_addr(u8 channel, bdaddr_t *src) |
{ |
struct sock *sk; |
|
for (sk = rfcomm_sk_list.head; sk; sk = sk->next) { |
if (rfcomm_pi(sk)->channel == channel && |
!bacmp(&bluez_pi(sk)->src, src)) |
break; |
} |
|
return sk; |
} |
|
/* Find socket with channel and source bdaddr. |
* Returns closest match. |
*/ |
static struct sock *__rfcomm_get_sock_by_channel(int state, u8 channel, bdaddr_t *src) |
{ |
struct sock *sk, *sk1 = NULL; |
|
for (sk = rfcomm_sk_list.head; sk; sk = sk->next) { |
if (state && sk->state != state) |
continue; |
|
if (rfcomm_pi(sk)->channel == channel) { |
/* Exact match. */ |
if (!bacmp(&bluez_pi(sk)->src, src)) |
break; |
|
/* Closest match */ |
if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY)) |
sk1 = sk; |
} |
} |
return sk ? sk : sk1; |
} |
|
/* Find socket with given address (channel, src). |
* Returns locked socket */ |
static inline struct sock *rfcomm_get_sock_by_channel(int state, u8 channel, bdaddr_t *src) |
{ |
struct sock *s; |
read_lock(&rfcomm_sk_list.lock); |
s = __rfcomm_get_sock_by_channel(state, channel, src); |
if (s) bh_lock_sock(s); |
read_unlock(&rfcomm_sk_list.lock); |
return s; |
} |
|
static void rfcomm_sock_destruct(struct sock *sk) |
{ |
struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc; |
|
BT_DBG("sk %p dlc %p", sk, d); |
|
skb_queue_purge(&sk->receive_queue); |
skb_queue_purge(&sk->write_queue); |
|
rfcomm_dlc_lock(d); |
rfcomm_pi(sk)->dlc = NULL; |
|
/* Detach DLC if it's owned by this socket */ |
if (d->owner == sk) |
d->owner = NULL; |
rfcomm_dlc_unlock(d); |
|
rfcomm_dlc_put(d); |
|
MOD_DEC_USE_COUNT; |
} |
|
static void rfcomm_sock_cleanup_listen(struct sock *parent) |
{ |
struct sock *sk; |
|
BT_DBG("parent %p", parent); |
|
/* Close not yet accepted dlcs */ |
while ((sk = bluez_accept_dequeue(parent, NULL))) { |
rfcomm_sock_close(sk); |
rfcomm_sock_kill(sk); |
} |
|
parent->state = BT_CLOSED; |
parent->zapped = 1; |
} |
|
/* Kill socket (only if zapped and orphan) |
* Must be called on unlocked socket. |
*/ |
static void rfcomm_sock_kill(struct sock *sk) |
{ |
if (!sk->zapped || sk->socket) |
return; |
|
BT_DBG("sk %p state %d refcnt %d", sk, sk->state, atomic_read(&sk->refcnt)); |
|
/* Kill poor orphan */ |
bluez_sock_unlink(&rfcomm_sk_list, sk); |
sk->dead = 1; |
sock_put(sk); |
} |
|
static void __rfcomm_sock_close(struct sock *sk) |
{ |
struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc; |
|
BT_DBG("sk %p state %d socket %p", sk, sk->state, sk->socket); |
|
switch (sk->state) { |
case BT_LISTEN: |
rfcomm_sock_cleanup_listen(sk); |
break; |
|
case BT_CONNECT: |
case BT_CONNECT2: |
case BT_CONFIG: |
case BT_CONNECTED: |
rfcomm_dlc_close(d, 0); |
|
default: |
sk->zapped = 1; |
break; |
} |
} |
|
/* Close socket. |
* Must be called on unlocked socket. |
*/ |
static void rfcomm_sock_close(struct sock *sk) |
{ |
lock_sock(sk); |
__rfcomm_sock_close(sk); |
release_sock(sk); |
} |
|
static void rfcomm_sock_init(struct sock *sk, struct sock *parent) |
{ |
BT_DBG("sk %p", sk); |
|
if (parent) |
sk->type = parent->type; |
} |
|
static struct sock *rfcomm_sock_alloc(struct socket *sock, int proto, int prio) |
{ |
struct rfcomm_dlc *d; |
struct sock *sk; |
|
sk = sk_alloc(PF_BLUETOOTH, prio, 1); |
if (!sk) |
return NULL; |
|
d = rfcomm_dlc_alloc(prio); |
if (!d) { |
sk_free(sk); |
return NULL; |
} |
d->data_ready = rfcomm_sk_data_ready; |
d->state_change = rfcomm_sk_state_change; |
|
rfcomm_pi(sk)->dlc = d; |
d->owner = sk; |
|
bluez_sock_init(sock, sk); |
|
sk->zapped = 0; |
|
sk->destruct = rfcomm_sock_destruct; |
sk->sndtimeo = RFCOMM_CONN_TIMEOUT; |
|
sk->sndbuf = RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 10; |
sk->rcvbuf = RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 10; |
|
sk->protocol = proto; |
sk->state = BT_OPEN; |
|
bluez_sock_link(&rfcomm_sk_list, sk); |
|
BT_DBG("sk %p", sk); |
|
MOD_INC_USE_COUNT; |
return sk; |
} |
|
static int rfcomm_sock_create(struct socket *sock, int protocol) |
{ |
struct sock *sk; |
|
BT_DBG("sock %p", sock); |
|
sock->state = SS_UNCONNECTED; |
|
if (sock->type != SOCK_STREAM && sock->type != SOCK_RAW) |
return -ESOCKTNOSUPPORT; |
|
sock->ops = &rfcomm_sock_ops; |
|
if (!(sk = rfcomm_sock_alloc(sock, protocol, GFP_KERNEL))) |
return -ENOMEM; |
|
rfcomm_sock_init(sk, NULL); |
return 0; |
} |
|
static int rfcomm_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) |
{ |
struct sockaddr_rc *sa = (struct sockaddr_rc *) addr; |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sk %p %s", sk, batostr(&sa->rc_bdaddr)); |
|
if (!addr || addr->sa_family != AF_BLUETOOTH) |
return -EINVAL; |
|
lock_sock(sk); |
|
if (sk->state != BT_OPEN) { |
err = -EBADFD; |
goto done; |
} |
|
write_lock_bh(&rfcomm_sk_list.lock); |
|
if (sa->rc_channel && __rfcomm_get_sock_by_addr(sa->rc_channel, &sa->rc_bdaddr)) { |
err = -EADDRINUSE; |
} else { |
/* Save source address */ |
bacpy(&bluez_pi(sk)->src, &sa->rc_bdaddr); |
rfcomm_pi(sk)->channel = sa->rc_channel; |
sk->state = BT_BOUND; |
} |
|
write_unlock_bh(&rfcomm_sk_list.lock); |
|
done: |
release_sock(sk); |
return err; |
} |
|
static int rfcomm_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags) |
{ |
struct sockaddr_rc *sa = (struct sockaddr_rc *) addr; |
struct sock *sk = sock->sk; |
struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc; |
int err = 0; |
|
BT_DBG("sk %p", sk); |
|
if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_rc)) |
return -EINVAL; |
|
if (sk->state != BT_OPEN && sk->state != BT_BOUND) |
return -EBADFD; |
|
if (sk->type != SOCK_STREAM) |
return -EINVAL; |
|
lock_sock(sk); |
|
sk->state = BT_CONNECT; |
bacpy(&bluez_pi(sk)->dst, &sa->rc_bdaddr); |
rfcomm_pi(sk)->channel = sa->rc_channel; |
|
err = rfcomm_dlc_open(d, &bluez_pi(sk)->src, &sa->rc_bdaddr, sa->rc_channel); |
if (!err) |
err = bluez_sock_wait_state(sk, BT_CONNECTED, |
sock_sndtimeo(sk, flags & O_NONBLOCK)); |
|
release_sock(sk); |
return err; |
} |
|
int rfcomm_sock_listen(struct socket *sock, int backlog) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sk %p backlog %d", sk, backlog); |
|
lock_sock(sk); |
|
if (sk->state != BT_BOUND) { |
err = -EBADFD; |
goto done; |
} |
|
sk->max_ack_backlog = backlog; |
sk->ack_backlog = 0; |
sk->state = BT_LISTEN; |
|
done: |
release_sock(sk); |
return err; |
} |
|
int rfcomm_sock_accept(struct socket *sock, struct socket *newsock, int flags) |
{ |
DECLARE_WAITQUEUE(wait, current); |
struct sock *sk = sock->sk, *nsk; |
long timeo; |
int err = 0; |
|
lock_sock(sk); |
|
if (sk->state != BT_LISTEN) { |
err = -EBADFD; |
goto done; |
} |
|
timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); |
|
BT_DBG("sk %p timeo %ld", sk, timeo); |
|
/* Wait for an incoming connection. (wake-one). */ |
add_wait_queue_exclusive(sk->sleep, &wait); |
while (!(nsk = bluez_accept_dequeue(sk, newsock))) { |
set_current_state(TASK_INTERRUPTIBLE); |
if (!timeo) { |
err = -EAGAIN; |
break; |
} |
|
release_sock(sk); |
timeo = schedule_timeout(timeo); |
lock_sock(sk); |
|
if (sk->state != BT_LISTEN) { |
err = -EBADFD; |
break; |
} |
|
if (signal_pending(current)) { |
err = sock_intr_errno(timeo); |
break; |
} |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(sk->sleep, &wait); |
|
if (err) |
goto done; |
|
newsock->state = SS_CONNECTED; |
|
BT_DBG("new socket %p", nsk); |
|
done: |
release_sock(sk); |
return err; |
} |
|
static int rfcomm_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer) |
{ |
struct sockaddr_rc *sa = (struct sockaddr_rc *) addr; |
struct sock *sk = sock->sk; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
sa->rc_family = AF_BLUETOOTH; |
sa->rc_channel = rfcomm_pi(sk)->channel; |
if (peer) |
bacpy(&sa->rc_bdaddr, &bluez_pi(sk)->dst); |
else |
bacpy(&sa->rc_bdaddr, &bluez_pi(sk)->src); |
|
*len = sizeof(struct sockaddr_rc); |
return 0; |
} |
|
static int rfcomm_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, |
struct scm_cookie *scm) |
{ |
struct sock *sk = sock->sk; |
struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc; |
struct sk_buff *skb; |
int err, size; |
int sent = 0; |
|
if (msg->msg_flags & MSG_OOB) |
return -EOPNOTSUPP; |
|
if (sk->shutdown & SEND_SHUTDOWN) |
return -EPIPE; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
lock_sock(sk); |
|
while (len) { |
size = min_t(uint, len, d->mtu); |
|
skb = sock_alloc_send_skb(sk, size + RFCOMM_SKB_RESERVE, |
msg->msg_flags & MSG_DONTWAIT, &err); |
if (!skb) |
break; |
skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE); |
|
err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size); |
if (err) { |
kfree_skb(skb); |
sent = err; |
break; |
} |
|
err = rfcomm_dlc_send(d, skb); |
if (err < 0) { |
kfree_skb(skb); |
break; |
} |
|
sent += size; |
len -= size; |
} |
|
release_sock(sk); |
|
return sent ? sent : err; |
} |
|
static long rfcomm_sock_data_wait(struct sock *sk, long timeo) |
{ |
DECLARE_WAITQUEUE(wait, current); |
|
add_wait_queue(sk->sleep, &wait); |
for (;;) { |
set_current_state(TASK_INTERRUPTIBLE); |
|
if (skb_queue_len(&sk->receive_queue) || sk->err || (sk->shutdown & RCV_SHUTDOWN) || |
signal_pending(current) || !timeo) |
break; |
|
set_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags); |
release_sock(sk); |
timeo = schedule_timeout(timeo); |
lock_sock(sk); |
clear_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags); |
} |
|
__set_current_state(TASK_RUNNING); |
remove_wait_queue(sk->sleep, &wait); |
return timeo; |
} |
|
static int rfcomm_sock_recvmsg(struct socket *sock, struct msghdr *msg, int size, |
int flags, struct scm_cookie *scm) |
{ |
struct sock *sk = sock->sk; |
int target, err = 0, copied = 0; |
long timeo; |
|
if (flags & MSG_OOB) |
return -EOPNOTSUPP; |
|
msg->msg_namelen = 0; |
|
BT_DBG("sk %p size %d", sk, size); |
|
lock_sock(sk); |
|
target = sock_rcvlowat(sk, flags & MSG_WAITALL, size); |
timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); |
|
do { |
struct sk_buff *skb; |
int chunk; |
|
skb = skb_dequeue(&sk->receive_queue); |
if (!skb) { |
if (copied >= target) |
break; |
|
if ((err = sock_error(sk)) != 0) |
break; |
if (sk->shutdown & RCV_SHUTDOWN) |
break; |
|
err = -EAGAIN; |
if (!timeo) |
break; |
|
timeo = rfcomm_sock_data_wait(sk, timeo); |
|
if (signal_pending(current)) { |
err = sock_intr_errno(timeo); |
goto out; |
} |
continue; |
} |
|
chunk = min_t(unsigned int, skb->len, size); |
if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) { |
skb_queue_head(&sk->receive_queue, skb); |
if (!copied) |
copied = -EFAULT; |
break; |
} |
copied += chunk; |
size -= chunk; |
|
if (!(flags & MSG_PEEK)) { |
atomic_sub(chunk, &sk->rmem_alloc); |
|
skb_pull(skb, chunk); |
if (skb->len) { |
skb_queue_head(&sk->receive_queue, skb); |
break; |
} |
kfree_skb(skb); |
|
} else { |
/* put message back and return */ |
skb_queue_head(&sk->receive_queue, skb); |
break; |
} |
} while (size); |
|
out: |
if (atomic_read(&sk->rmem_alloc) <= (sk->rcvbuf >> 2)) |
rfcomm_dlc_unthrottle(rfcomm_pi(sk)->dlc); |
|
release_sock(sk); |
return copied ? : err; |
} |
|
static int rfcomm_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sk %p", sk); |
|
lock_sock(sk); |
|
switch (optname) { |
default: |
err = -ENOPROTOOPT; |
break; |
}; |
|
release_sock(sk); |
return err; |
} |
|
static int rfcomm_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen) |
{ |
struct sock *sk = sock->sk; |
int len, err = 0; |
|
BT_DBG("sk %p", sk); |
|
if (get_user(len, optlen)) |
return -EFAULT; |
|
lock_sock(sk); |
|
switch (optname) { |
default: |
err = -ENOPROTOOPT; |
break; |
}; |
|
release_sock(sk); |
return err; |
} |
|
static int rfcomm_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) |
{ |
struct sock *sk = sock->sk; |
int err; |
|
lock_sock(sk); |
|
#ifdef CONFIG_BLUEZ_RFCOMM_TTY |
err = rfcomm_dev_ioctl(sk, cmd, arg); |
#else |
err = -EOPNOTSUPP; |
#endif |
|
release_sock(sk); |
|
return err; |
} |
|
static int rfcomm_sock_shutdown(struct socket *sock, int how) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
if (!sk) return 0; |
|
lock_sock(sk); |
if (!sk->shutdown) { |
sk->shutdown = SHUTDOWN_MASK; |
__rfcomm_sock_close(sk); |
|
if (sk->linger) |
err = bluez_sock_wait_state(sk, BT_CLOSED, sk->lingertime); |
} |
release_sock(sk); |
return err; |
} |
|
static int rfcomm_sock_release(struct socket *sock) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
if (!sk) |
return 0; |
|
err = rfcomm_sock_shutdown(sock, 2); |
|
sock_orphan(sk); |
rfcomm_sock_kill(sk); |
return err; |
} |
|
/* ---- RFCOMM core layer callbacks ---- |
* |
* called under rfcomm_lock() |
*/ |
int rfcomm_connect_ind(struct rfcomm_session *s, u8 channel, struct rfcomm_dlc **d) |
{ |
struct sock *sk, *parent; |
bdaddr_t src, dst; |
int result = 0; |
|
BT_DBG("session %p channel %d", s, channel); |
|
rfcomm_session_getaddr(s, &src, &dst); |
|
/* Check if we have socket listening on this channel */ |
parent = rfcomm_get_sock_by_channel(BT_LISTEN, channel, &src); |
if (!parent) |
return 0; |
|
/* Check for backlog size */ |
if (parent->ack_backlog > parent->max_ack_backlog) { |
BT_DBG("backlog full %d", parent->ack_backlog); |
goto done; |
} |
|
sk = rfcomm_sock_alloc(NULL, BTPROTO_RFCOMM, GFP_ATOMIC); |
if (!sk) |
goto done; |
|
rfcomm_sock_init(sk, parent); |
bacpy(&bluez_pi(sk)->src, &src); |
bacpy(&bluez_pi(sk)->dst, &dst); |
rfcomm_pi(sk)->channel = channel; |
|
sk->state = BT_CONFIG; |
bluez_accept_enqueue(parent, sk); |
|
/* Accept connection and return socket DLC */ |
*d = rfcomm_pi(sk)->dlc; |
result = 1; |
|
done: |
bh_unlock_sock(parent); |
return result; |
} |
|
/* ---- Proc fs support ---- */ |
int rfcomm_sock_dump(char *buf) |
{ |
struct bluez_sock_list *list = &rfcomm_sk_list; |
struct rfcomm_pinfo *pi; |
struct sock *sk; |
char *ptr = buf; |
|
write_lock_bh(&list->lock); |
|
for (sk = list->head; sk; sk = sk->next) { |
pi = rfcomm_pi(sk); |
ptr += sprintf(ptr, "sk %s %s %d %d\n", |
batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst), |
sk->state, rfcomm_pi(sk)->channel); |
} |
|
write_unlock_bh(&list->lock); |
|
return ptr - buf; |
} |
|
static struct proto_ops rfcomm_sock_ops = { |
family: PF_BLUETOOTH, |
release: rfcomm_sock_release, |
bind: rfcomm_sock_bind, |
connect: rfcomm_sock_connect, |
listen: rfcomm_sock_listen, |
accept: rfcomm_sock_accept, |
getname: rfcomm_sock_getname, |
sendmsg: rfcomm_sock_sendmsg, |
recvmsg: rfcomm_sock_recvmsg, |
shutdown: rfcomm_sock_shutdown, |
setsockopt: rfcomm_sock_setsockopt, |
getsockopt: rfcomm_sock_getsockopt, |
ioctl: rfcomm_sock_ioctl, |
poll: bluez_sock_poll, |
socketpair: sock_no_socketpair, |
mmap: sock_no_mmap |
}; |
|
static struct net_proto_family rfcomm_sock_family_ops = { |
family: PF_BLUETOOTH, |
create: rfcomm_sock_create |
}; |
|
int rfcomm_init_sockets(void) |
{ |
int err; |
|
if ((err = bluez_sock_register(BTPROTO_RFCOMM, &rfcomm_sock_family_ops))) { |
BT_ERR("Can't register RFCOMM socket layer"); |
return err; |
} |
|
return 0; |
} |
|
void rfcomm_cleanup_sockets(void) |
{ |
int err; |
|
/* Unregister socket, protocol and notifier */ |
if ((err = bluez_sock_unregister(BTPROTO_RFCOMM))) |
BT_ERR("Can't unregister RFCOMM socket layer %d", err); |
} |
/rfcomm/tty.c
0,0 → 1,958
/* |
RFCOMM implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com> |
Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org> |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* RFCOMM TTY. |
* |
* $Id: tty.c,v 1.1.1.1 2004-04-15 01:17:10 phoenix Exp $ |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/tty.h> |
#include <linux/tty_driver.h> |
#include <linux/tty_flip.h> |
|
#include <linux/slab.h> |
#include <linux/skbuff.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/rfcomm.h> |
|
#ifndef CONFIG_BLUEZ_RFCOMM_DEBUG |
#undef BT_DBG |
#define BT_DBG(D...) |
#endif |
|
#define RFCOMM_TTY_MAGIC 0x6d02 /* magic number for rfcomm struct */ |
#define RFCOMM_TTY_PORTS RFCOMM_MAX_DEV /* whole lotta rfcomm devices */ |
#define RFCOMM_TTY_MAJOR 216 /* device node major id of the usb/bluetooth.c driver */ |
#define RFCOMM_TTY_MINOR 0 |
|
struct rfcomm_dev { |
struct list_head list; |
atomic_t refcnt; |
|
char name[12]; |
int id; |
unsigned long flags; |
int opened; |
int err; |
|
bdaddr_t src; |
bdaddr_t dst; |
u8 channel; |
|
uint modem_status; |
|
struct rfcomm_dlc *dlc; |
struct tty_struct *tty; |
wait_queue_head_t wait; |
struct tasklet_struct wakeup_task; |
|
atomic_t wmem_alloc; |
}; |
|
static LIST_HEAD(rfcomm_dev_list); |
static rwlock_t rfcomm_dev_lock = RW_LOCK_UNLOCKED; |
|
static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb); |
static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err); |
static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig); |
|
static void rfcomm_tty_wakeup(unsigned long arg); |
|
/* ---- Device functions ---- */ |
static void rfcomm_dev_destruct(struct rfcomm_dev *dev) |
{ |
struct rfcomm_dlc *dlc = dev->dlc; |
|
BT_DBG("dev %p dlc %p", dev, dlc); |
|
rfcomm_dlc_lock(dlc); |
/* Detach DLC if it's owned by this dev */ |
if (dlc->owner == dev) |
dlc->owner = NULL; |
rfcomm_dlc_unlock(dlc); |
|
rfcomm_dlc_put(dlc); |
kfree(dev); |
|
MOD_DEC_USE_COUNT; |
} |
|
static inline void rfcomm_dev_hold(struct rfcomm_dev *dev) |
{ |
atomic_inc(&dev->refcnt); |
} |
|
static inline void rfcomm_dev_put(struct rfcomm_dev *dev) |
{ |
/* The reason this isn't actually a race, as you no |
doubt have a little voice screaming at you in your |
head, is that the refcount should never actually |
reach zero unless the device has already been taken |
off the list, in rfcomm_dev_del(). And if that's not |
true, we'll hit the BUG() in rfcomm_dev_destruct() |
anyway. */ |
if (atomic_dec_and_test(&dev->refcnt)) |
rfcomm_dev_destruct(dev); |
} |
|
static struct rfcomm_dev *__rfcomm_dev_get(int id) |
{ |
struct rfcomm_dev *dev; |
struct list_head *p; |
|
list_for_each(p, &rfcomm_dev_list) { |
dev = list_entry(p, struct rfcomm_dev, list); |
if (dev->id == id) |
return dev; |
} |
|
return NULL; |
} |
|
static inline struct rfcomm_dev *rfcomm_dev_get(int id) |
{ |
struct rfcomm_dev *dev; |
|
read_lock(&rfcomm_dev_lock); |
|
dev = __rfcomm_dev_get(id); |
if (dev) |
rfcomm_dev_hold(dev); |
|
read_unlock(&rfcomm_dev_lock); |
|
return dev; |
} |
|
static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc) |
{ |
struct rfcomm_dev *dev; |
struct list_head *head = &rfcomm_dev_list, *p; |
int err = 0; |
|
BT_DBG("id %d channel %d", req->dev_id, req->channel); |
|
dev = kmalloc(sizeof(struct rfcomm_dev), GFP_KERNEL); |
if (!dev) |
return -ENOMEM; |
memset(dev, 0, sizeof(struct rfcomm_dev)); |
|
write_lock_bh(&rfcomm_dev_lock); |
|
if (req->dev_id < 0) { |
dev->id = 0; |
|
list_for_each(p, &rfcomm_dev_list) { |
if (list_entry(p, struct rfcomm_dev, list)->id != dev->id) |
break; |
|
dev->id++; |
head = p; |
} |
} else { |
dev->id = req->dev_id; |
|
list_for_each(p, &rfcomm_dev_list) { |
struct rfcomm_dev *entry = list_entry(p, struct rfcomm_dev, list); |
|
if (entry->id == dev->id) { |
err = -EADDRINUSE; |
goto out; |
} |
|
if (entry->id > dev->id - 1) |
break; |
|
head = p; |
} |
} |
|
if ((dev->id < 0) || (dev->id > RFCOMM_MAX_DEV - 1)) { |
err = -ENFILE; |
goto out; |
} |
|
sprintf(dev->name, "rfcomm%d", dev->id); |
|
list_add(&dev->list, head); |
atomic_set(&dev->refcnt, 1); |
|
bacpy(&dev->src, &req->src); |
bacpy(&dev->dst, &req->dst); |
dev->channel = req->channel; |
|
dev->flags = req->flags & |
((1 << RFCOMM_RELEASE_ONHUP) | (1 << RFCOMM_REUSE_DLC)); |
|
init_waitqueue_head(&dev->wait); |
tasklet_init(&dev->wakeup_task, rfcomm_tty_wakeup, (unsigned long) dev); |
|
rfcomm_dlc_lock(dlc); |
dlc->data_ready = rfcomm_dev_data_ready; |
dlc->state_change = rfcomm_dev_state_change; |
dlc->modem_status = rfcomm_dev_modem_status; |
|
dlc->owner = dev; |
dev->dlc = dlc; |
rfcomm_dlc_unlock(dlc); |
|
MOD_INC_USE_COUNT; |
|
out: |
write_unlock_bh(&rfcomm_dev_lock); |
|
if (err) { |
kfree(dev); |
return err; |
} else |
return dev->id; |
} |
|
static void rfcomm_dev_del(struct rfcomm_dev *dev) |
{ |
BT_DBG("dev %p", dev); |
|
write_lock_bh(&rfcomm_dev_lock); |
list_del_init(&dev->list); |
write_unlock_bh(&rfcomm_dev_lock); |
|
rfcomm_dev_put(dev); |
} |
|
/* ---- Send buffer ---- */ |
|
static inline unsigned int rfcomm_room(struct rfcomm_dlc *dlc) |
{ |
/* We can't let it be zero, because we don't get a callback |
when tx_credits becomes nonzero, hence we'd never wake up */ |
return dlc->mtu * (dlc->tx_credits?:1); |
} |
|
static void rfcomm_wfree(struct sk_buff *skb) |
{ |
struct rfcomm_dev *dev = (void *) skb->sk; |
atomic_sub(skb->truesize, &dev->wmem_alloc); |
if (test_bit(RFCOMM_TTY_ATTACHED, &dev->flags)) |
tasklet_schedule(&dev->wakeup_task); |
rfcomm_dev_put(dev); |
} |
|
static inline void rfcomm_set_owner_w(struct sk_buff *skb, struct rfcomm_dev *dev) |
{ |
rfcomm_dev_hold(dev); |
atomic_add(skb->truesize, &dev->wmem_alloc); |
skb->sk = (void *) dev; |
skb->destructor = rfcomm_wfree; |
} |
|
static struct sk_buff *rfcomm_wmalloc(struct rfcomm_dev *dev, unsigned long size, int force, int priority) |
{ |
if (force || atomic_read(&dev->wmem_alloc) < rfcomm_room(dev->dlc)) { |
struct sk_buff *skb = alloc_skb(size, priority); |
if (skb) { |
rfcomm_set_owner_w(skb, dev); |
return skb; |
} |
} |
return NULL; |
} |
|
/* ---- Device IOCTLs ---- */ |
|
#define NOCAP_FLAGS ((1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP)) |
|
static int rfcomm_create_dev(struct sock *sk, unsigned long arg) |
{ |
struct rfcomm_dev_req req; |
struct rfcomm_dlc *dlc; |
int id; |
|
if (copy_from_user(&req, (void *) arg, sizeof(req))) |
return -EFAULT; |
|
BT_DBG("sk %p dev_id %id flags 0x%x", sk, req.dev_id, req.flags); |
|
if (req.flags != NOCAP_FLAGS && !capable(CAP_NET_ADMIN)) |
return -EPERM; |
|
if (req.flags & (1 << RFCOMM_REUSE_DLC)) { |
/* Socket must be connected */ |
if (sk->state != BT_CONNECTED) |
return -EBADFD; |
|
dlc = rfcomm_pi(sk)->dlc; |
rfcomm_dlc_hold(dlc); |
} else { |
dlc = rfcomm_dlc_alloc(GFP_KERNEL); |
if (!dlc) |
return -ENOMEM; |
} |
|
id = rfcomm_dev_add(&req, dlc); |
if (id < 0) { |
rfcomm_dlc_put(dlc); |
return id; |
} |
|
if (req.flags & (1 << RFCOMM_REUSE_DLC)) { |
/* DLC is now used by device. |
* Socket must be disconnected */ |
sk->state = BT_CLOSED; |
} |
|
return id; |
} |
|
static int rfcomm_release_dev(unsigned long arg) |
{ |
struct rfcomm_dev_req req; |
struct rfcomm_dev *dev; |
|
if (copy_from_user(&req, (void *) arg, sizeof(req))) |
return -EFAULT; |
|
BT_DBG("dev_id %id flags 0x%x", req.dev_id, req.flags); |
|
if (!capable(CAP_NET_ADMIN)) |
return -EPERM; |
|
if (!(dev = rfcomm_dev_get(req.dev_id))) |
return -ENODEV; |
|
if (req.flags & (1 << RFCOMM_HANGUP_NOW)) |
rfcomm_dlc_close(dev->dlc, 0); |
|
rfcomm_dev_del(dev); |
rfcomm_dev_put(dev); |
return 0; |
} |
|
static int rfcomm_get_dev_list(unsigned long arg) |
{ |
struct rfcomm_dev_list_req *dl; |
struct rfcomm_dev_info *di; |
struct list_head *p; |
int n = 0, size, err; |
u16 dev_num; |
|
BT_DBG(""); |
|
if (get_user(dev_num, (u16 *) arg)) |
return -EFAULT; |
|
if (!dev_num || dev_num > (PAGE_SIZE * 4) / sizeof(*di)) |
return -EINVAL; |
|
size = sizeof(*dl) + dev_num * sizeof(*di); |
|
if (!(dl = kmalloc(size, GFP_KERNEL))) |
return -ENOMEM; |
|
di = dl->dev_info; |
|
read_lock_bh(&rfcomm_dev_lock); |
|
list_for_each(p, &rfcomm_dev_list) { |
struct rfcomm_dev *dev = list_entry(p, struct rfcomm_dev, list); |
(di + n)->id = dev->id; |
(di + n)->flags = dev->flags; |
(di + n)->state = dev->dlc->state; |
(di + n)->channel = dev->channel; |
bacpy(&(di + n)->src, &dev->src); |
bacpy(&(di + n)->dst, &dev->dst); |
if (++n >= dev_num) |
break; |
} |
|
read_unlock_bh(&rfcomm_dev_lock); |
|
dl->dev_num = n; |
size = sizeof(*dl) + n * sizeof(*di); |
|
err = copy_to_user((void *) arg, dl, size); |
kfree(dl); |
|
return err ? -EFAULT : 0; |
} |
|
static int rfcomm_get_dev_info(unsigned long arg) |
{ |
struct rfcomm_dev *dev; |
struct rfcomm_dev_info di; |
int err = 0; |
|
BT_DBG(""); |
|
if (copy_from_user(&di, (void *)arg, sizeof(di))) |
return -EFAULT; |
|
if (!(dev = rfcomm_dev_get(di.id))) |
return -ENODEV; |
|
di.flags = dev->flags; |
di.channel = dev->channel; |
di.state = dev->dlc->state; |
bacpy(&di.src, &dev->src); |
bacpy(&di.dst, &dev->dst); |
|
if (copy_to_user((void *)arg, &di, sizeof(di))) |
err = -EFAULT; |
|
rfcomm_dev_put(dev); |
return err; |
} |
|
int rfcomm_dev_ioctl(struct sock *sk, unsigned int cmd, unsigned long arg) |
{ |
BT_DBG("cmd %d arg %ld", cmd, arg); |
|
switch (cmd) { |
case RFCOMMCREATEDEV: |
return rfcomm_create_dev(sk, arg); |
|
case RFCOMMRELEASEDEV: |
return rfcomm_release_dev(arg); |
|
case RFCOMMGETDEVLIST: |
return rfcomm_get_dev_list(arg); |
|
case RFCOMMGETDEVINFO: |
return rfcomm_get_dev_info(arg); |
} |
|
return -EINVAL; |
} |
|
/* ---- DLC callbacks ---- */ |
static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb) |
{ |
struct rfcomm_dev *dev = dlc->owner; |
struct tty_struct *tty; |
|
if (!dev || !(tty = dev->tty)) { |
kfree_skb(skb); |
return; |
} |
|
BT_DBG("dlc %p tty %p len %d", dlc, tty, skb->len); |
|
if (test_bit(TTY_DONT_FLIP, &tty->flags)) { |
register int i; |
for (i = 0; i < skb->len; i++) { |
if (tty->flip.count >= TTY_FLIPBUF_SIZE) |
tty_flip_buffer_push(tty); |
|
tty_insert_flip_char(tty, skb->data[i], 0); |
} |
tty_flip_buffer_push(tty); |
} else |
tty->ldisc.receive_buf(tty, skb->data, NULL, skb->len); |
|
kfree_skb(skb); |
} |
|
static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err) |
{ |
struct rfcomm_dev *dev = dlc->owner; |
if (!dev) |
return; |
|
BT_DBG("dlc %p dev %p err %d", dlc, dev, err); |
|
dev->err = err; |
wake_up_interruptible(&dev->wait); |
|
if (dlc->state == BT_CLOSED) { |
if (!dev->tty) { |
if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { |
rfcomm_dev_hold(dev); |
rfcomm_dev_del(dev); |
|
/* We have to drop DLC lock here, otherwise |
rfcomm_dev_put() will dead lock if it's |
the last reference. */ |
rfcomm_dlc_unlock(dlc); |
rfcomm_dev_put(dev); |
rfcomm_dlc_lock(dlc); |
} |
} else |
tty_hangup(dev->tty); |
} |
} |
|
static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig) |
{ |
struct rfcomm_dev *dev = dlc->owner; |
if (!dev) |
return; |
|
BT_DBG("dlc %p dev %p v24_sig 0x%02x", dlc, dev, v24_sig); |
|
dev->modem_status = |
((v24_sig & RFCOMM_V24_RTC) ? (TIOCM_DSR | TIOCM_DTR) : 0) | |
((v24_sig & RFCOMM_V24_RTR) ? (TIOCM_RTS | TIOCM_CTS) : 0) | |
((v24_sig & RFCOMM_V24_IC) ? TIOCM_RI : 0) | |
((v24_sig & RFCOMM_V24_DV) ? TIOCM_CD : 0); |
} |
|
/* ---- TTY functions ---- */ |
static void rfcomm_tty_wakeup(unsigned long arg) |
{ |
struct rfcomm_dev *dev = (void *) arg; |
struct tty_struct *tty = dev->tty; |
if (!tty) |
return; |
|
BT_DBG("dev %p tty %p", dev, tty); |
|
if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) && tty->ldisc.write_wakeup) |
(tty->ldisc.write_wakeup)(tty); |
|
wake_up_interruptible(&tty->write_wait); |
#ifdef SERIAL_HAVE_POLL_WAIT |
wake_up_interruptible(&tty->poll_wait); |
#endif |
} |
|
static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp) |
{ |
DECLARE_WAITQUEUE(wait, current); |
struct rfcomm_dev *dev; |
struct rfcomm_dlc *dlc; |
int err, id; |
|
id = MINOR(tty->device) - tty->driver.minor_start; |
|
BT_DBG("tty %p id %d", tty, id); |
|
/* We don't leak this refcount. For reasons which are not entirely |
clear, the TTY layer will call our ->close() method even if the |
open fails. We decrease the refcount there, and decreasing it |
here too would cause breakage. */ |
dev = rfcomm_dev_get(id); |
if (!dev) |
return -ENODEV; |
|
BT_DBG("dev %p dst %s channel %d opened %d", dev, batostr(&dev->dst), dev->channel, dev->opened); |
|
if (dev->opened++ != 0) |
return 0; |
|
dlc = dev->dlc; |
|
/* Attach TTY and open DLC */ |
|
rfcomm_dlc_lock(dlc); |
tty->driver_data = dev; |
dev->tty = tty; |
rfcomm_dlc_unlock(dlc); |
set_bit(RFCOMM_TTY_ATTACHED, &dev->flags); |
|
err = rfcomm_dlc_open(dlc, &dev->src, &dev->dst, dev->channel); |
if (err < 0) |
return err; |
|
/* Wait for DLC to connect */ |
add_wait_queue(&dev->wait, &wait); |
while (1) { |
set_current_state(TASK_INTERRUPTIBLE); |
|
if (dlc->state == BT_CLOSED) { |
err = -dev->err; |
break; |
} |
|
if (dlc->state == BT_CONNECTED) |
break; |
|
if (signal_pending(current)) { |
err = -EINTR; |
break; |
} |
|
schedule(); |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(&dev->wait, &wait); |
|
return err; |
} |
|
static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
if (!dev) |
return; |
|
BT_DBG("tty %p dev %p dlc %p opened %d", tty, dev, dev->dlc, dev->opened); |
|
if (--dev->opened == 0) { |
/* Close DLC and dettach TTY */ |
rfcomm_dlc_close(dev->dlc, 0); |
|
clear_bit(RFCOMM_TTY_ATTACHED, &dev->flags); |
tasklet_kill(&dev->wakeup_task); |
|
rfcomm_dlc_lock(dev->dlc); |
tty->driver_data = NULL; |
dev->tty = NULL; |
rfcomm_dlc_unlock(dev->dlc); |
} |
|
rfcomm_dev_put(dev); |
} |
|
static int rfcomm_tty_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
struct rfcomm_dlc *dlc = dev->dlc; |
struct sk_buff *skb; |
int err = 0, sent = 0, size; |
|
BT_DBG("tty %p from_user %d count %d", tty, from_user, count); |
|
while (count) { |
size = min_t(uint, count, dlc->mtu); |
|
if (from_user) |
skb = rfcomm_wmalloc(dev, size + RFCOMM_SKB_RESERVE, 0, GFP_KERNEL); |
else |
skb = rfcomm_wmalloc(dev, size + RFCOMM_SKB_RESERVE, 0, GFP_ATOMIC); |
|
if (!skb) |
break; |
|
skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE); |
|
if (from_user) |
copy_from_user(skb_put(skb, size), buf + sent, size); |
else |
memcpy(skb_put(skb, size), buf + sent, size); |
|
if ((err = rfcomm_dlc_send(dlc, skb)) < 0) { |
kfree_skb(skb); |
break; |
} |
|
sent += size; |
count -= size; |
} |
|
return sent ? sent : err; |
} |
|
static void rfcomm_tty_put_char(struct tty_struct *tty, unsigned char ch) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
struct rfcomm_dlc *dlc = dev->dlc; |
struct sk_buff *skb; |
|
BT_DBG("tty %p char %x", tty, ch); |
|
skb = rfcomm_wmalloc(dev, 1 + RFCOMM_SKB_RESERVE, 1, GFP_ATOMIC); |
|
if (!skb) |
return; |
|
skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE); |
|
*(char *)skb_put(skb, 1) = ch; |
|
if ((rfcomm_dlc_send(dlc, skb)) < 0) |
kfree_skb(skb); |
} |
|
static int rfcomm_tty_write_room(struct tty_struct *tty) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
int room; |
|
BT_DBG("tty %p", tty); |
|
room = rfcomm_room(dev->dlc) - atomic_read(&dev->wmem_alloc); |
if (room < 0) |
room = 0; |
|
return room; |
} |
|
static int rfcomm_tty_set_modem_status(uint cmd, struct rfcomm_dlc *dlc, uint status) |
{ |
u8 v24_sig, mask; |
|
BT_DBG("dlc %p cmd 0x%02x", dlc, cmd); |
|
if (cmd == TIOCMSET) |
v24_sig = 0; |
else |
rfcomm_dlc_get_modem_status(dlc, &v24_sig); |
|
mask = ((status & TIOCM_DSR) ? RFCOMM_V24_RTC : 0) | |
((status & TIOCM_DTR) ? RFCOMM_V24_RTC : 0) | |
((status & TIOCM_RTS) ? RFCOMM_V24_RTR : 0) | |
((status & TIOCM_CTS) ? RFCOMM_V24_RTR : 0) | |
((status & TIOCM_RI) ? RFCOMM_V24_IC : 0) | |
((status & TIOCM_CD) ? RFCOMM_V24_DV : 0); |
|
if (cmd == TIOCMBIC) |
v24_sig &= ~mask; |
else |
v24_sig |= mask; |
|
rfcomm_dlc_set_modem_status(dlc, v24_sig); |
return 0; |
} |
|
static int rfcomm_tty_ioctl(struct tty_struct *tty, struct file *filp, unsigned int cmd, unsigned long arg) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
struct rfcomm_dlc *dlc = dev->dlc; |
uint status; |
int err; |
|
BT_DBG("tty %p cmd 0x%02x", tty, cmd); |
|
switch (cmd) { |
case TCGETS: |
BT_DBG("TCGETS is not supported"); |
return -ENOIOCTLCMD; |
|
case TCSETS: |
BT_DBG("TCSETS is not supported"); |
return -ENOIOCTLCMD; |
|
case TIOCMGET: |
BT_DBG("TIOCMGET"); |
|
return put_user(dev->modem_status, (unsigned int *)arg); |
|
case TIOCMSET: /* Turns on and off the lines as specified by the mask */ |
case TIOCMBIS: /* Turns on the lines as specified by the mask */ |
case TIOCMBIC: /* Turns off the lines as specified by the mask */ |
if ((err = get_user(status, (unsigned int *)arg))) |
return err; |
return rfcomm_tty_set_modem_status(cmd, dlc, status); |
|
case TIOCMIWAIT: |
BT_DBG("TIOCMIWAIT"); |
break; |
|
case TIOCGICOUNT: |
BT_DBG("TIOCGICOUNT"); |
break; |
|
case TIOCGSERIAL: |
BT_ERR("TIOCGSERIAL is not supported"); |
return -ENOIOCTLCMD; |
|
case TIOCSSERIAL: |
BT_ERR("TIOCSSERIAL is not supported"); |
return -ENOIOCTLCMD; |
|
case TIOCSERGSTRUCT: |
BT_ERR("TIOCSERGSTRUCT is not supported"); |
return -ENOIOCTLCMD; |
|
case TIOCSERGETLSR: |
BT_ERR("TIOCSERGETLSR is not supported"); |
return -ENOIOCTLCMD; |
|
case TIOCSERCONFIG: |
BT_ERR("TIOCSERCONFIG is not supported"); |
return -ENOIOCTLCMD; |
|
default: |
return -ENOIOCTLCMD; /* ioctls which we must ignore */ |
|
} |
|
return -ENOIOCTLCMD; |
} |
|
#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) |
|
static void rfcomm_tty_set_termios(struct tty_struct *tty, struct termios *old) |
{ |
BT_DBG("tty %p", tty); |
|
if ((tty->termios->c_cflag == old->c_cflag) && |
(RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old->c_iflag))) |
return; |
|
/* handle turning off CRTSCTS */ |
if ((old->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) { |
BT_DBG("turning off CRTSCTS"); |
} |
} |
|
static void rfcomm_tty_throttle(struct tty_struct *tty) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
|
BT_DBG("tty %p dev %p", tty, dev); |
|
rfcomm_dlc_throttle(dev->dlc); |
} |
|
static void rfcomm_tty_unthrottle(struct tty_struct *tty) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
|
BT_DBG("tty %p dev %p", tty, dev); |
|
rfcomm_dlc_unthrottle(dev->dlc); |
} |
|
static int rfcomm_tty_chars_in_buffer(struct tty_struct *tty) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
struct rfcomm_dlc *dlc = dev->dlc; |
|
BT_DBG("tty %p dev %p", tty, dev); |
|
if (skb_queue_len(&dlc->tx_queue)) |
return dlc->mtu; |
|
return 0; |
} |
|
static void rfcomm_tty_flush_buffer(struct tty_struct *tty) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
if (!dev) |
return; |
|
BT_DBG("tty %p dev %p", tty, dev); |
|
skb_queue_purge(&dev->dlc->tx_queue); |
|
if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) && tty->ldisc.write_wakeup) |
tty->ldisc.write_wakeup(tty); |
} |
|
static void rfcomm_tty_send_xchar(struct tty_struct *tty, char ch) |
{ |
BT_DBG("tty %p ch %c", tty, ch); |
} |
|
static void rfcomm_tty_wait_until_sent(struct tty_struct *tty, int timeout) |
{ |
BT_DBG("tty %p timeout %d", tty, timeout); |
} |
|
static void rfcomm_tty_hangup(struct tty_struct *tty) |
{ |
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; |
if (!dev) |
return; |
|
BT_DBG("tty %p dev %p", tty, dev); |
|
rfcomm_tty_flush_buffer(tty); |
|
if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) |
rfcomm_dev_del(dev); |
} |
|
static int rfcomm_tty_read_proc(char *buf, char **start, off_t offset, int len, int *eof, void *unused) |
{ |
return 0; |
} |
|
/* ---- TTY structure ---- */ |
static int rfcomm_tty_refcount; /* If we manage several devices */ |
|
static struct tty_struct *rfcomm_tty_table[RFCOMM_TTY_PORTS]; |
static struct termios *rfcomm_tty_termios[RFCOMM_TTY_PORTS]; |
static struct termios *rfcomm_tty_termios_locked[RFCOMM_TTY_PORTS]; |
|
static struct tty_driver rfcomm_tty_driver = { |
magic: TTY_DRIVER_MAGIC, |
driver_name: "rfcomm", |
#ifdef CONFIG_DEVFS_FS |
name: "bluetooth/rfcomm/%d", |
#else |
name: "rfcomm", |
#endif |
major: RFCOMM_TTY_MAJOR, |
minor_start: RFCOMM_TTY_MINOR, |
num: RFCOMM_TTY_PORTS, |
type: TTY_DRIVER_TYPE_SERIAL, |
subtype: SERIAL_TYPE_NORMAL, |
flags: TTY_DRIVER_REAL_RAW, |
|
refcount: &rfcomm_tty_refcount, |
table: rfcomm_tty_table, |
termios: rfcomm_tty_termios, |
termios_locked: rfcomm_tty_termios_locked, |
|
open: rfcomm_tty_open, |
close: rfcomm_tty_close, |
put_char: rfcomm_tty_put_char, |
write: rfcomm_tty_write, |
write_room: rfcomm_tty_write_room, |
chars_in_buffer: rfcomm_tty_chars_in_buffer, |
flush_buffer: rfcomm_tty_flush_buffer, |
ioctl: rfcomm_tty_ioctl, |
throttle: rfcomm_tty_throttle, |
unthrottle: rfcomm_tty_unthrottle, |
set_termios: rfcomm_tty_set_termios, |
send_xchar: rfcomm_tty_send_xchar, |
stop: NULL, |
start: NULL, |
hangup: rfcomm_tty_hangup, |
wait_until_sent: rfcomm_tty_wait_until_sent, |
read_proc: rfcomm_tty_read_proc, |
}; |
|
int rfcomm_init_ttys(void) |
{ |
int i; |
|
/* Initalize our global data */ |
for (i = 0; i < RFCOMM_TTY_PORTS; i++) |
rfcomm_tty_table[i] = NULL; |
|
/* Register the TTY driver */ |
rfcomm_tty_driver.init_termios = tty_std_termios; |
rfcomm_tty_driver.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; |
rfcomm_tty_driver.flags = TTY_DRIVER_REAL_RAW; |
|
if (tty_register_driver(&rfcomm_tty_driver)) { |
BT_ERR("Can't register RFCOMM TTY driver"); |
return -1; |
} |
|
return 0; |
} |
|
void rfcomm_cleanup_ttys(void) |
{ |
tty_unregister_driver(&rfcomm_tty_driver); |
return; |
} |
/rfcomm/crc.c
0,0 → 1,71
/* |
RFCOMM implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com> |
Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org> |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* RFCOMM FCS calculation. |
* |
* $Id: crc.c,v 1.1.1.1 2004-04-15 01:17:09 phoenix Exp $ |
*/ |
|
/* reversed, 8-bit, poly=0x07 */ |
unsigned char rfcomm_crc_table[256] = { |
0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, |
0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, |
0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, |
0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, |
|
0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, |
0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, |
0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, |
0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, |
|
0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, |
0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, |
0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, |
0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, |
|
0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, |
0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, |
0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, |
0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, |
|
0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, |
0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, |
0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, |
0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, |
|
0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, |
0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, |
0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, |
0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, |
|
0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, |
0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, |
0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, |
0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, |
|
0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, |
0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, |
0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, |
0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf |
}; |
/rfcomm/core.c
0,0 → 1,1940
/* |
RFCOMM implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com> |
Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org> |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
RPN support - Dirk Husemann <hud@zurich.ibm.com> |
*/ |
|
/* |
* RFCOMM core. |
* |
* $Id: core.c,v 1.1.1.1 2004-04-15 01:17:12 phoenix Exp $ |
*/ |
|
#define __KERNEL_SYSCALLS__ |
|
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/sched.h> |
#include <linux/signal.h> |
#include <linux/init.h> |
#include <linux/wait.h> |
#include <linux/net.h> |
#include <linux/proc_fs.h> |
#include <net/sock.h> |
#include <asm/uaccess.h> |
#include <asm/unaligned.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/l2cap.h> |
#include <net/bluetooth/rfcomm.h> |
|
#define VERSION "1.1" |
|
#ifndef CONFIG_BLUEZ_RFCOMM_DEBUG |
#undef BT_DBG |
#define BT_DBG(D...) |
#endif |
|
struct task_struct *rfcomm_thread; |
DECLARE_MUTEX(rfcomm_sem); |
unsigned long rfcomm_event; |
|
static LIST_HEAD(session_list); |
static atomic_t terminate, running; |
|
static int rfcomm_send_frame(struct rfcomm_session *s, u8 *data, int len); |
static int rfcomm_send_sabm(struct rfcomm_session *s, u8 dlci); |
static int rfcomm_send_disc(struct rfcomm_session *s, u8 dlci); |
static int rfcomm_queue_disc(struct rfcomm_dlc *d); |
static int rfcomm_send_nsc(struct rfcomm_session *s, int cr, u8 type); |
static int rfcomm_send_pn(struct rfcomm_session *s, int cr, struct rfcomm_dlc *d); |
static int rfcomm_send_msc(struct rfcomm_session *s, int cr, u8 dlci, u8 v24_sig); |
static int rfcomm_send_test(struct rfcomm_session *s, int cr, u8 *pattern, int len); |
static int rfcomm_send_credits(struct rfcomm_session *s, u8 addr, u8 credits); |
static void rfcomm_make_uih(struct sk_buff *skb, u8 addr); |
|
static void rfcomm_process_connect(struct rfcomm_session *s); |
|
/* ---- RFCOMM frame parsing macros ---- */ |
#define __get_dlci(b) ((b & 0xfc) >> 2) |
#define __get_channel(b) ((b & 0xf8) >> 3) |
#define __get_dir(b) ((b & 0x04) >> 2) |
#define __get_type(b) ((b & 0xef)) |
|
#define __test_ea(b) ((b & 0x01)) |
#define __test_cr(b) ((b & 0x02)) |
#define __test_pf(b) ((b & 0x10)) |
|
#define __addr(cr, dlci) (((dlci & 0x3f) << 2) | (cr << 1) | 0x01) |
#define __ctrl(type, pf) (((type & 0xef) | (pf << 4))) |
#define __dlci(dir, chn) (((chn & 0x1f) << 1) | dir) |
#define __srv_channel(dlci) (dlci >> 1) |
#define __dir(dlci) (dlci & 0x01) |
|
#define __len8(len) (((len) << 1) | 1) |
#define __len16(len) ((len) << 1) |
|
/* MCC macros */ |
#define __mcc_type(cr, type) (((type << 2) | (cr << 1) | 0x01)) |
#define __get_mcc_type(b) ((b & 0xfc) >> 2) |
#define __get_mcc_len(b) ((b & 0xfe) >> 1) |
|
/* RPN macros */ |
#define __rpn_line_settings(data, stop, parity) ((data & 0x3) | ((stop & 0x1) << 2) | ((parity & 0x3) << 3)) |
#define __get_rpn_data_bits(line) ((line) & 0x3) |
#define __get_rpn_stop_bits(line) (((line) >> 2) & 0x1) |
#define __get_rpn_parity(line) (((line) >> 3) & 0x3) |
|
/* ---- RFCOMM FCS computation ---- */ |
|
/* CRC on 2 bytes */ |
#define __crc(data) (rfcomm_crc_table[rfcomm_crc_table[0xff ^ data[0]] ^ data[1]]) |
|
/* FCS on 2 bytes */ |
static inline u8 __fcs(u8 *data) |
{ |
return (0xff - __crc(data)); |
} |
|
/* FCS on 3 bytes */ |
static inline u8 __fcs2(u8 *data) |
{ |
return (0xff - rfcomm_crc_table[__crc(data) ^ data[2]]); |
} |
|
/* Check FCS */ |
static inline int __check_fcs(u8 *data, int type, u8 fcs) |
{ |
u8 f = __crc(data); |
|
if (type != RFCOMM_UIH) |
f = rfcomm_crc_table[f ^ data[2]]; |
|
return rfcomm_crc_table[f ^ fcs] != 0xcf; |
} |
|
/* ---- L2CAP callbacks ---- */ |
static void rfcomm_l2state_change(struct sock *sk) |
{ |
BT_DBG("%p state %d", sk, sk->state); |
rfcomm_schedule(RFCOMM_SCHED_STATE); |
} |
|
static void rfcomm_l2data_ready(struct sock *sk, int bytes) |
{ |
BT_DBG("%p bytes %d", sk, bytes); |
rfcomm_schedule(RFCOMM_SCHED_RX); |
} |
|
static int rfcomm_l2sock_create(struct socket **sock) |
{ |
int err; |
|
BT_DBG(""); |
|
err = sock_create(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP, sock); |
if (!err) { |
struct sock *sk = (*sock)->sk; |
sk->data_ready = rfcomm_l2data_ready; |
sk->state_change = rfcomm_l2state_change; |
} |
return err; |
} |
|
/* ---- RFCOMM DLCs ---- */ |
static void rfcomm_dlc_timeout(unsigned long arg) |
{ |
struct rfcomm_dlc *d = (void *) arg; |
|
BT_DBG("dlc %p state %ld", d, d->state); |
|
set_bit(RFCOMM_TIMED_OUT, &d->flags); |
rfcomm_dlc_put(d); |
rfcomm_schedule(RFCOMM_SCHED_TIMEO); |
} |
|
static void rfcomm_dlc_set_timer(struct rfcomm_dlc *d, long timeout) |
{ |
BT_DBG("dlc %p state %ld timeout %ld", d, d->state, timeout); |
|
if (!mod_timer(&d->timer, jiffies + timeout)) |
rfcomm_dlc_hold(d); |
} |
|
static void rfcomm_dlc_clear_timer(struct rfcomm_dlc *d) |
{ |
BT_DBG("dlc %p state %ld", d, d->state); |
|
if (timer_pending(&d->timer) && del_timer(&d->timer)) |
rfcomm_dlc_put(d); |
} |
|
static void rfcomm_dlc_clear_state(struct rfcomm_dlc *d) |
{ |
BT_DBG("%p", d); |
|
d->state = BT_OPEN; |
d->flags = 0; |
d->mscex = 0; |
d->mtu = RFCOMM_DEFAULT_MTU; |
d->v24_sig = RFCOMM_V24_RTC | RFCOMM_V24_RTR | RFCOMM_V24_DV; |
|
d->cfc = RFCOMM_CFC_DISABLED; |
d->rx_credits = RFCOMM_DEFAULT_CREDITS; |
} |
|
struct rfcomm_dlc *rfcomm_dlc_alloc(int prio) |
{ |
struct rfcomm_dlc *d = kmalloc(sizeof(*d), prio); |
if (!d) |
return NULL; |
memset(d, 0, sizeof(*d)); |
|
init_timer(&d->timer); |
d->timer.function = rfcomm_dlc_timeout; |
d->timer.data = (unsigned long) d; |
|
skb_queue_head_init(&d->tx_queue); |
spin_lock_init(&d->lock); |
atomic_set(&d->refcnt, 1); |
|
rfcomm_dlc_clear_state(d); |
|
BT_DBG("%p", d); |
return d; |
} |
|
void rfcomm_dlc_free(struct rfcomm_dlc *d) |
{ |
BT_DBG("%p", d); |
|
skb_queue_purge(&d->tx_queue); |
kfree(d); |
} |
|
static void rfcomm_dlc_link(struct rfcomm_session *s, struct rfcomm_dlc *d) |
{ |
BT_DBG("dlc %p session %p", d, s); |
|
rfcomm_session_hold(s); |
|
rfcomm_dlc_hold(d); |
list_add(&d->list, &s->dlcs); |
d->session = s; |
} |
|
static void rfcomm_dlc_unlink(struct rfcomm_dlc *d) |
{ |
struct rfcomm_session *s = d->session; |
|
BT_DBG("dlc %p refcnt %d session %p", d, atomic_read(&d->refcnt), s); |
|
list_del(&d->list); |
d->session = NULL; |
rfcomm_dlc_put(d); |
|
rfcomm_session_put(s); |
} |
|
static struct rfcomm_dlc *rfcomm_dlc_get(struct rfcomm_session *s, u8 dlci) |
{ |
struct rfcomm_dlc *d; |
struct list_head *p; |
|
list_for_each(p, &s->dlcs) { |
d = list_entry(p, struct rfcomm_dlc, list); |
if (d->dlci == dlci) |
return d; |
} |
return NULL; |
} |
|
static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) |
{ |
struct rfcomm_session *s; |
int err = 0; |
u8 dlci; |
|
BT_DBG("dlc %p state %ld %s %s channel %d", |
d, d->state, batostr(src), batostr(dst), channel); |
|
if (channel < 1 || channel > 30) |
return -EINVAL; |
|
if (d->state != BT_OPEN && d->state != BT_CLOSED) |
return 0; |
|
s = rfcomm_session_get(src, dst); |
if (!s) { |
s = rfcomm_session_create(src, dst, &err); |
if (!s) |
return err; |
} |
|
dlci = __dlci(!s->initiator, channel); |
|
/* Check if DLCI already exists */ |
if (rfcomm_dlc_get(s, dlci)) |
return -EBUSY; |
|
rfcomm_dlc_clear_state(d); |
|
d->dlci = dlci; |
d->addr = __addr(s->initiator, dlci); |
d->priority = 7; |
|
d->state = BT_CONFIG; |
rfcomm_dlc_link(s, d); |
|
d->mtu = s->mtu; |
d->cfc = (s->cfc == RFCOMM_CFC_UNKNOWN) ? 0 : s->cfc; |
|
if (s->state == BT_CONNECTED) |
rfcomm_send_pn(s, 1, d); |
rfcomm_dlc_set_timer(d, RFCOMM_CONN_TIMEOUT); |
return 0; |
} |
|
int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) |
{ |
mm_segment_t fs; |
int r; |
|
rfcomm_lock(); |
|
fs = get_fs(); set_fs(KERNEL_DS); |
r = __rfcomm_dlc_open(d, src, dst, channel); |
set_fs(fs); |
|
rfcomm_unlock(); |
return r; |
} |
|
static int __rfcomm_dlc_close(struct rfcomm_dlc *d, int err) |
{ |
struct rfcomm_session *s = d->session; |
if (!s) |
return 0; |
|
BT_DBG("dlc %p state %ld dlci %d err %d session %p", |
d, d->state, d->dlci, err, s); |
|
switch (d->state) { |
case BT_CONNECTED: |
case BT_CONFIG: |
case BT_CONNECT: |
d->state = BT_DISCONN; |
if (skb_queue_empty(&d->tx_queue)) { |
rfcomm_send_disc(s, d->dlci); |
rfcomm_dlc_set_timer(d, RFCOMM_DISC_TIMEOUT); |
} else { |
rfcomm_queue_disc(d); |
rfcomm_dlc_set_timer(d, RFCOMM_DISC_TIMEOUT * 2); |
} |
break; |
|
default: |
rfcomm_dlc_clear_timer(d); |
|
rfcomm_dlc_lock(d); |
d->state = BT_CLOSED; |
d->state_change(d, err); |
rfcomm_dlc_unlock(d); |
|
skb_queue_purge(&d->tx_queue); |
rfcomm_dlc_unlink(d); |
} |
|
return 0; |
} |
|
int rfcomm_dlc_close(struct rfcomm_dlc *d, int err) |
{ |
mm_segment_t fs; |
int r; |
|
rfcomm_lock(); |
|
fs = get_fs(); set_fs(KERNEL_DS); |
r = __rfcomm_dlc_close(d, err); |
set_fs(fs); |
|
rfcomm_unlock(); |
return r; |
} |
|
int rfcomm_dlc_send(struct rfcomm_dlc *d, struct sk_buff *skb) |
{ |
int len = skb->len; |
|
if (d->state != BT_CONNECTED) |
return -ENOTCONN; |
|
BT_DBG("dlc %p mtu %d len %d", d, d->mtu, len); |
|
if (len > d->mtu) |
return -EINVAL; |
|
rfcomm_make_uih(skb, d->addr); |
skb_queue_tail(&d->tx_queue, skb); |
|
if (!test_bit(RFCOMM_TX_THROTTLED, &d->flags)) |
rfcomm_schedule(RFCOMM_SCHED_TX); |
return len; |
} |
|
void __rfcomm_dlc_throttle(struct rfcomm_dlc *d) |
{ |
BT_DBG("dlc %p state %ld", d, d->state); |
|
if (!d->cfc) { |
d->v24_sig |= RFCOMM_V24_FC; |
set_bit(RFCOMM_MSC_PENDING, &d->flags); |
} |
rfcomm_schedule(RFCOMM_SCHED_TX); |
} |
|
void __rfcomm_dlc_unthrottle(struct rfcomm_dlc *d) |
{ |
BT_DBG("dlc %p state %ld", d, d->state); |
|
if (!d->cfc) { |
d->v24_sig &= ~RFCOMM_V24_FC; |
set_bit(RFCOMM_MSC_PENDING, &d->flags); |
} |
rfcomm_schedule(RFCOMM_SCHED_TX); |
} |
|
/* |
Set/get modem status functions use _local_ status i.e. what we report |
to the other side. |
Remote status is provided by dlc->modem_status() callback. |
*/ |
int rfcomm_dlc_set_modem_status(struct rfcomm_dlc *d, u8 v24_sig) |
{ |
BT_DBG("dlc %p state %ld v24_sig 0x%x", |
d, d->state, v24_sig); |
|
if (test_bit(RFCOMM_RX_THROTTLED, &d->flags)) |
v24_sig |= RFCOMM_V24_FC; |
else |
v24_sig &= ~RFCOMM_V24_FC; |
|
d->v24_sig = v24_sig; |
|
if (!test_and_set_bit(RFCOMM_MSC_PENDING, &d->flags)) |
rfcomm_schedule(RFCOMM_SCHED_TX); |
|
return 0; |
} |
|
int rfcomm_dlc_get_modem_status(struct rfcomm_dlc *d, u8 *v24_sig) |
{ |
BT_DBG("dlc %p state %ld v24_sig 0x%x", |
d, d->state, d->v24_sig); |
|
*v24_sig = d->v24_sig; |
return 0; |
} |
|
/* ---- RFCOMM sessions ---- */ |
struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state) |
{ |
struct rfcomm_session *s = kmalloc(sizeof(*s), GFP_KERNEL); |
if (!s) |
return NULL; |
memset(s, 0, sizeof(*s)); |
|
BT_DBG("session %p sock %p", s, sock); |
|
INIT_LIST_HEAD(&s->dlcs); |
s->state = state; |
s->sock = sock; |
|
s->mtu = RFCOMM_DEFAULT_MTU; |
s->cfc = RFCOMM_CFC_UNKNOWN; |
|
list_add(&s->list, &session_list); |
|
/* Do not increment module usage count for listeting sessions. |
* Otherwise we won't be able to unload the module. */ |
if (state != BT_LISTEN) |
MOD_INC_USE_COUNT; |
return s; |
} |
|
void rfcomm_session_del(struct rfcomm_session *s) |
{ |
int state = s->state; |
|
BT_DBG("session %p state %ld", s, s->state); |
|
list_del(&s->list); |
|
if (state == BT_CONNECTED) |
rfcomm_send_disc(s, 0); |
|
sock_release(s->sock); |
kfree(s); |
|
if (state != BT_LISTEN) |
MOD_DEC_USE_COUNT; |
} |
|
struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst) |
{ |
struct rfcomm_session *s; |
struct list_head *p, *n; |
struct bluez_pinfo *pi; |
list_for_each_safe(p, n, &session_list) { |
s = list_entry(p, struct rfcomm_session, list); |
pi = bluez_pi(s->sock->sk); |
|
if ((!bacmp(src, BDADDR_ANY) || !bacmp(&pi->src, src)) && |
!bacmp(&pi->dst, dst)) |
return s; |
} |
return NULL; |
} |
|
void rfcomm_session_close(struct rfcomm_session *s, int err) |
{ |
struct rfcomm_dlc *d; |
struct list_head *p, *n; |
|
BT_DBG("session %p state %ld err %d", s, s->state, err); |
|
rfcomm_session_hold(s); |
|
s->state = BT_CLOSED; |
|
/* Close all dlcs */ |
list_for_each_safe(p, n, &s->dlcs) { |
d = list_entry(p, struct rfcomm_dlc, list); |
d->state = BT_CLOSED; |
__rfcomm_dlc_close(d, err); |
} |
|
rfcomm_session_put(s); |
} |
|
struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, bdaddr_t *dst, int *err) |
{ |
struct rfcomm_session *s = NULL; |
struct sockaddr_l2 addr; |
struct l2cap_options opts; |
struct socket *sock; |
int size; |
|
BT_DBG("%s %s", batostr(src), batostr(dst)); |
|
*err = rfcomm_l2sock_create(&sock); |
if (*err < 0) |
return NULL; |
|
bacpy(&addr.l2_bdaddr, src); |
addr.l2_family = AF_BLUETOOTH; |
addr.l2_psm = 0; |
*err = sock->ops->bind(sock, (struct sockaddr *) &addr, sizeof(addr)); |
if (*err < 0) |
goto failed; |
|
/* Set L2CAP options */ |
size = sizeof(opts); |
sock->ops->getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, &size); |
|
opts.imtu = RFCOMM_MAX_L2CAP_MTU; |
sock->ops->setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, size); |
|
s = rfcomm_session_add(sock, BT_BOUND); |
if (!s) { |
*err = -ENOMEM; |
goto failed; |
} |
|
s->initiator = 1; |
|
bacpy(&addr.l2_bdaddr, dst); |
addr.l2_family = AF_BLUETOOTH; |
addr.l2_psm = htobs(RFCOMM_PSM); |
*err = sock->ops->connect(sock, (struct sockaddr *) &addr, sizeof(addr), O_NONBLOCK); |
if (*err == 0 || *err == -EAGAIN) |
return s; |
|
rfcomm_session_del(s); |
return NULL; |
|
failed: |
sock_release(sock); |
return NULL; |
} |
|
void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src, bdaddr_t *dst) |
{ |
struct sock *sk = s->sock->sk; |
if (src) |
bacpy(src, &bluez_pi(sk)->src); |
if (dst) |
bacpy(dst, &bluez_pi(sk)->dst); |
} |
|
/* ---- RFCOMM frame sending ---- */ |
static int rfcomm_send_frame(struct rfcomm_session *s, u8 *data, int len) |
{ |
struct socket *sock = s->sock; |
struct iovec iv = { data, len }; |
struct msghdr msg; |
int err; |
|
BT_DBG("session %p len %d", s, len); |
|
memset(&msg, 0, sizeof(msg)); |
msg.msg_iovlen = 1; |
msg.msg_iov = &iv; |
|
err = sock->ops->sendmsg(sock, &msg, len, 0); |
return err; |
} |
|
static int rfcomm_send_sabm(struct rfcomm_session *s, u8 dlci) |
{ |
struct rfcomm_cmd cmd; |
|
BT_DBG("%p dlci %d", s, dlci); |
|
cmd.addr = __addr(s->initiator, dlci); |
cmd.ctrl = __ctrl(RFCOMM_SABM, 1); |
cmd.len = __len8(0); |
cmd.fcs = __fcs2((u8 *) &cmd); |
|
return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); |
} |
|
static int rfcomm_send_ua(struct rfcomm_session *s, u8 dlci) |
{ |
struct rfcomm_cmd cmd; |
|
BT_DBG("%p dlci %d", s, dlci); |
|
cmd.addr = __addr(!s->initiator, dlci); |
cmd.ctrl = __ctrl(RFCOMM_UA, 1); |
cmd.len = __len8(0); |
cmd.fcs = __fcs2((u8 *) &cmd); |
|
return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); |
} |
|
static int rfcomm_send_disc(struct rfcomm_session *s, u8 dlci) |
{ |
struct rfcomm_cmd cmd; |
|
BT_DBG("%p dlci %d", s, dlci); |
|
cmd.addr = __addr(s->initiator, dlci); |
cmd.ctrl = __ctrl(RFCOMM_DISC, 1); |
cmd.len = __len8(0); |
cmd.fcs = __fcs2((u8 *) &cmd); |
|
return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); |
} |
|
static int rfcomm_queue_disc(struct rfcomm_dlc *d) |
{ |
struct rfcomm_cmd *cmd; |
struct sk_buff *skb; |
|
BT_DBG("dlc %p dlci %d", d, d->dlci); |
|
skb = alloc_skb(sizeof(*cmd), GFP_KERNEL); |
if (!skb) |
return -ENOMEM; |
|
cmd = (void *) __skb_put(skb, sizeof(*cmd)); |
cmd->addr = d->addr; |
cmd->ctrl = __ctrl(RFCOMM_DISC, 1); |
cmd->len = __len8(0); |
cmd->fcs = __fcs2((u8 *) cmd); |
|
skb_queue_tail(&d->tx_queue, skb); |
rfcomm_schedule(RFCOMM_SCHED_TX); |
return 0; |
} |
|
static int rfcomm_send_dm(struct rfcomm_session *s, u8 dlci) |
{ |
struct rfcomm_cmd cmd; |
|
BT_DBG("%p dlci %d", s, dlci); |
|
cmd.addr = __addr(!s->initiator, dlci); |
cmd.ctrl = __ctrl(RFCOMM_DM, 1); |
cmd.len = __len8(0); |
cmd.fcs = __fcs2((u8 *) &cmd); |
|
return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); |
} |
|
static int rfcomm_send_nsc(struct rfcomm_session *s, int cr, u8 type) |
{ |
struct rfcomm_hdr *hdr; |
struct rfcomm_mcc *mcc; |
u8 buf[16], *ptr = buf; |
|
BT_DBG("%p cr %d type %d", s, cr, type); |
|
hdr = (void *) ptr; ptr += sizeof(*hdr); |
hdr->addr = __addr(s->initiator, 0); |
hdr->ctrl = __ctrl(RFCOMM_UIH, 0); |
hdr->len = __len8(sizeof(*mcc) + 1); |
|
mcc = (void *) ptr; ptr += sizeof(*mcc); |
mcc->type = __mcc_type(cr, RFCOMM_NSC); |
mcc->len = __len8(1); |
|
/* Type that we didn't like */ |
*ptr = __mcc_type(cr, type); ptr++; |
|
*ptr = __fcs(buf); ptr++; |
|
return rfcomm_send_frame(s, buf, ptr - buf); |
} |
|
static int rfcomm_send_pn(struct rfcomm_session *s, int cr, struct rfcomm_dlc *d) |
{ |
struct rfcomm_hdr *hdr; |
struct rfcomm_mcc *mcc; |
struct rfcomm_pn *pn; |
u8 buf[16], *ptr = buf; |
|
BT_DBG("%p cr %d dlci %d mtu %d", s, cr, d->dlci, d->mtu); |
|
hdr = (void *) ptr; ptr += sizeof(*hdr); |
hdr->addr = __addr(s->initiator, 0); |
hdr->ctrl = __ctrl(RFCOMM_UIH, 0); |
hdr->len = __len8(sizeof(*mcc) + sizeof(*pn)); |
|
mcc = (void *) ptr; ptr += sizeof(*mcc); |
mcc->type = __mcc_type(cr, RFCOMM_PN); |
mcc->len = __len8(sizeof(*pn)); |
|
pn = (void *) ptr; ptr += sizeof(*pn); |
pn->dlci = d->dlci; |
pn->priority = d->priority; |
pn->ack_timer = 0; |
pn->max_retrans = 0; |
|
if (s->cfc) { |
pn->flow_ctrl = cr ? 0xf0 : 0xe0; |
pn->credits = RFCOMM_DEFAULT_CREDITS; |
} else { |
pn->flow_ctrl = 0; |
pn->credits = 0; |
} |
|
pn->mtu = htobs(d->mtu); |
|
*ptr = __fcs(buf); ptr++; |
|
return rfcomm_send_frame(s, buf, ptr - buf); |
} |
|
static int rfcomm_send_rpn(struct rfcomm_session *s, int cr, u8 dlci, |
u8 bit_rate, u8 data_bits, u8 stop_bits, |
u8 parity, u8 flow_ctrl_settings, |
u8 xon_char, u8 xoff_char, u16 param_mask) |
{ |
struct rfcomm_hdr *hdr; |
struct rfcomm_mcc *mcc; |
struct rfcomm_rpn *rpn; |
u8 buf[16], *ptr = buf; |
|
BT_DBG("%p cr %d dlci %d bit_r 0x%x data_b 0x%x stop_b 0x%x parity 0x%x" |
"flwc_s 0x%x xon_c 0x%x xoff_c 0x%x p_mask 0x%x", |
s, cr, dlci, bit_rate, data_bits, stop_bits, parity, |
flow_ctrl_settings, xon_char, xoff_char, param_mask); |
|
hdr = (void *) ptr; ptr += sizeof(*hdr); |
hdr->addr = __addr(s->initiator, 0); |
hdr->ctrl = __ctrl(RFCOMM_UIH, 0); |
hdr->len = __len8(sizeof(*mcc) + sizeof(*rpn)); |
|
mcc = (void *) ptr; ptr += sizeof(*mcc); |
mcc->type = __mcc_type(cr, RFCOMM_RPN); |
mcc->len = __len8(sizeof(*rpn)); |
|
rpn = (void *) ptr; ptr += sizeof(*rpn); |
rpn->dlci = __addr(1, dlci); |
rpn->bit_rate = bit_rate; |
rpn->line_settings = __rpn_line_settings(data_bits, stop_bits, parity); |
rpn->flow_ctrl = flow_ctrl_settings; |
rpn->xon_char = xon_char; |
rpn->xoff_char = xoff_char; |
rpn->param_mask = param_mask; |
|
*ptr = __fcs(buf); ptr++; |
|
return rfcomm_send_frame(s, buf, ptr - buf); |
} |
|
static int rfcomm_send_rls(struct rfcomm_session *s, int cr, u8 dlci, u8 status) |
{ |
struct rfcomm_hdr *hdr; |
struct rfcomm_mcc *mcc; |
struct rfcomm_rls *rls; |
u8 buf[16], *ptr = buf; |
|
BT_DBG("%p cr %d status 0x%x", s, cr, status); |
|
hdr = (void *) ptr; ptr += sizeof(*hdr); |
hdr->addr = __addr(s->initiator, 0); |
hdr->ctrl = __ctrl(RFCOMM_UIH, 0); |
hdr->len = __len8(sizeof(*mcc) + sizeof(*rls)); |
|
mcc = (void *) ptr; ptr += sizeof(*mcc); |
mcc->type = __mcc_type(cr, RFCOMM_RLS); |
mcc->len = __len8(sizeof(*rls)); |
|
rls = (void *) ptr; ptr += sizeof(*rls); |
rls->dlci = __addr(1, dlci); |
rls->status = status; |
|
*ptr = __fcs(buf); ptr++; |
|
return rfcomm_send_frame(s, buf, ptr - buf); |
} |
|
static int rfcomm_send_msc(struct rfcomm_session *s, int cr, u8 dlci, u8 v24_sig) |
{ |
struct rfcomm_hdr *hdr; |
struct rfcomm_mcc *mcc; |
struct rfcomm_msc *msc; |
u8 buf[16], *ptr = buf; |
|
BT_DBG("%p cr %d v24 0x%x", s, cr, v24_sig); |
|
hdr = (void *) ptr; ptr += sizeof(*hdr); |
hdr->addr = __addr(s->initiator, 0); |
hdr->ctrl = __ctrl(RFCOMM_UIH, 0); |
hdr->len = __len8(sizeof(*mcc) + sizeof(*msc)); |
|
mcc = (void *) ptr; ptr += sizeof(*mcc); |
mcc->type = __mcc_type(cr, RFCOMM_MSC); |
mcc->len = __len8(sizeof(*msc)); |
|
msc = (void *) ptr; ptr += sizeof(*msc); |
msc->dlci = __addr(1, dlci); |
msc->v24_sig = v24_sig | 0x01; |
|
*ptr = __fcs(buf); ptr++; |
|
return rfcomm_send_frame(s, buf, ptr - buf); |
} |
|
static int rfcomm_send_fcoff(struct rfcomm_session *s, int cr) |
{ |
struct rfcomm_hdr *hdr; |
struct rfcomm_mcc *mcc; |
u8 buf[16], *ptr = buf; |
|
BT_DBG("%p cr %d", s, cr); |
|
hdr = (void *) ptr; ptr += sizeof(*hdr); |
hdr->addr = __addr(s->initiator, 0); |
hdr->ctrl = __ctrl(RFCOMM_UIH, 0); |
hdr->len = __len8(sizeof(*mcc)); |
|
mcc = (void *) ptr; ptr += sizeof(*mcc); |
mcc->type = __mcc_type(cr, RFCOMM_FCOFF); |
mcc->len = __len8(0); |
|
*ptr = __fcs(buf); ptr++; |
|
return rfcomm_send_frame(s, buf, ptr - buf); |
} |
|
static int rfcomm_send_fcon(struct rfcomm_session *s, int cr) |
{ |
struct rfcomm_hdr *hdr; |
struct rfcomm_mcc *mcc; |
u8 buf[16], *ptr = buf; |
|
BT_DBG("%p cr %d", s, cr); |
|
hdr = (void *) ptr; ptr += sizeof(*hdr); |
hdr->addr = __addr(s->initiator, 0); |
hdr->ctrl = __ctrl(RFCOMM_UIH, 0); |
hdr->len = __len8(sizeof(*mcc)); |
|
mcc = (void *) ptr; ptr += sizeof(*mcc); |
mcc->type = __mcc_type(cr, RFCOMM_FCON); |
mcc->len = __len8(0); |
|
*ptr = __fcs(buf); ptr++; |
|
return rfcomm_send_frame(s, buf, ptr - buf); |
} |
|
static int rfcomm_send_test(struct rfcomm_session *s, int cr, u8 *pattern, int len) |
{ |
struct socket *sock = s->sock; |
struct iovec iv[3]; |
struct msghdr msg; |
unsigned char hdr[5], crc[1]; |
|
if (len > 125) |
return -EINVAL; |
|
BT_DBG("%p cr %d", s, cr); |
|
hdr[0] = __addr(s->initiator, 0); |
hdr[1] = __ctrl(RFCOMM_UIH, 0); |
hdr[2] = 0x01 | ((len + 2) << 1); |
hdr[3] = 0x01 | ((cr & 0x01) << 1) | (RFCOMM_TEST << 2); |
hdr[4] = 0x01 | (len << 1); |
|
crc[0] = __fcs(hdr); |
|
iv[0].iov_base = hdr; |
iv[0].iov_len = 5; |
iv[1].iov_base = pattern; |
iv[1].iov_len = len; |
iv[2].iov_base = crc; |
iv[2].iov_len = 1; |
|
memset(&msg, 0, sizeof(msg)); |
msg.msg_iovlen = 3; |
msg.msg_iov = iv; |
return sock->ops->sendmsg(sock, &msg, 6 + len, 0); |
} |
|
static int rfcomm_send_credits(struct rfcomm_session *s, u8 addr, u8 credits) |
{ |
struct rfcomm_hdr *hdr; |
u8 buf[16], *ptr = buf; |
|
BT_DBG("%p addr %d credits %d", s, addr, credits); |
|
hdr = (void *) ptr; ptr += sizeof(*hdr); |
hdr->addr = addr; |
hdr->ctrl = __ctrl(RFCOMM_UIH, 1); |
hdr->len = __len8(0); |
|
*ptr = credits; ptr++; |
|
*ptr = __fcs(buf); ptr++; |
|
return rfcomm_send_frame(s, buf, ptr - buf); |
} |
|
static void rfcomm_make_uih(struct sk_buff *skb, u8 addr) |
{ |
struct rfcomm_hdr *hdr; |
int len = skb->len; |
u8 *crc; |
|
if (len > 127) { |
hdr = (void *) skb_push(skb, 4); |
put_unaligned(htobs(__len16(len)), (u16 *) &hdr->len); |
} else { |
hdr = (void *) skb_push(skb, 3); |
hdr->len = __len8(len); |
} |
hdr->addr = addr; |
hdr->ctrl = __ctrl(RFCOMM_UIH, 0); |
|
crc = skb_put(skb, 1); |
*crc = __fcs((void *) hdr); |
} |
|
/* ---- RFCOMM frame reception ---- */ |
static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) |
{ |
BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); |
|
if (dlci) { |
/* Data channel */ |
struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci); |
if (!d) { |
rfcomm_send_dm(s, dlci); |
return 0; |
} |
|
switch (d->state) { |
case BT_CONNECT: |
rfcomm_dlc_clear_timer(d); |
|
rfcomm_dlc_lock(d); |
d->state = BT_CONNECTED; |
d->state_change(d, 0); |
rfcomm_dlc_unlock(d); |
|
rfcomm_send_msc(s, 1, dlci, d->v24_sig); |
break; |
|
case BT_DISCONN: |
d->state = BT_CLOSED; |
__rfcomm_dlc_close(d, 0); |
break; |
} |
} else { |
/* Control channel */ |
switch (s->state) { |
case BT_CONNECT: |
s->state = BT_CONNECTED; |
rfcomm_process_connect(s); |
break; |
} |
} |
return 0; |
} |
|
static int rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci) |
{ |
int err = 0; |
|
BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); |
|
if (dlci) { |
/* Data DLC */ |
struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci); |
if (d) { |
if (d->state == BT_CONNECT || d->state == BT_CONFIG) |
err = ECONNREFUSED; |
else |
err = ECONNRESET; |
|
d->state = BT_CLOSED; |
__rfcomm_dlc_close(d, err); |
} |
} else { |
if (s->state == BT_CONNECT) |
err = ECONNREFUSED; |
else |
err = ECONNRESET; |
|
s->state = BT_CLOSED; |
rfcomm_session_close(s, err); |
} |
return 0; |
} |
|
static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci) |
{ |
int err = 0; |
|
BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); |
|
if (dlci) { |
struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci); |
if (d) { |
rfcomm_send_ua(s, dlci); |
|
if (d->state == BT_CONNECT || d->state == BT_CONFIG) |
err = ECONNREFUSED; |
else |
err = ECONNRESET; |
|
d->state = BT_CLOSED; |
__rfcomm_dlc_close(d, err); |
} else |
rfcomm_send_dm(s, dlci); |
|
} else { |
rfcomm_send_ua(s, 0); |
|
if (s->state == BT_CONNECT) |
err = ECONNREFUSED; |
else |
err = ECONNRESET; |
|
s->state = BT_CLOSED; |
rfcomm_session_close(s, err); |
} |
|
return 0; |
} |
|
static int rfcomm_recv_sabm(struct rfcomm_session *s, u8 dlci) |
{ |
struct rfcomm_dlc *d; |
u8 channel; |
|
BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); |
|
if (!dlci) { |
rfcomm_send_ua(s, 0); |
|
if (s->state == BT_OPEN) { |
s->state = BT_CONNECTED; |
rfcomm_process_connect(s); |
} |
return 0; |
} |
|
/* Check if DLC exists */ |
d = rfcomm_dlc_get(s, dlci); |
if (d) { |
if (d->state == BT_OPEN) { |
/* DLC was previously opened by PN request */ |
rfcomm_send_ua(s, dlci); |
|
rfcomm_dlc_lock(d); |
d->state = BT_CONNECTED; |
d->state_change(d, 0); |
rfcomm_dlc_unlock(d); |
|
rfcomm_send_msc(s, 1, dlci, d->v24_sig); |
} |
return 0; |
} |
|
/* Notify socket layer about incomming connection */ |
channel = __srv_channel(dlci); |
if (rfcomm_connect_ind(s, channel, &d)) { |
d->dlci = dlci; |
d->addr = __addr(s->initiator, dlci); |
rfcomm_dlc_link(s, d); |
|
rfcomm_send_ua(s, dlci); |
|
rfcomm_dlc_lock(d); |
d->state = BT_CONNECTED; |
d->state_change(d, 0); |
rfcomm_dlc_unlock(d); |
|
rfcomm_send_msc(s, 1, dlci, d->v24_sig); |
} else { |
rfcomm_send_dm(s, dlci); |
} |
|
return 0; |
} |
|
static int rfcomm_apply_pn(struct rfcomm_dlc *d, int cr, struct rfcomm_pn *pn) |
{ |
struct rfcomm_session *s = d->session; |
|
BT_DBG("dlc %p state %ld dlci %d mtu %d fc 0x%x credits %d", |
d, d->state, d->dlci, pn->mtu, pn->flow_ctrl, pn->credits); |
|
if (pn->flow_ctrl == 0xf0 || pn->flow_ctrl == 0xe0) { |
d->cfc = s->cfc = RFCOMM_CFC_ENABLED; |
d->tx_credits = pn->credits; |
} else { |
d->cfc = s->cfc = RFCOMM_CFC_DISABLED; |
set_bit(RFCOMM_TX_THROTTLED, &d->flags); |
} |
|
d->priority = pn->priority; |
|
d->mtu = s->mtu = btohs(pn->mtu); |
|
return 0; |
} |
|
static int rfcomm_recv_pn(struct rfcomm_session *s, int cr, struct sk_buff *skb) |
{ |
struct rfcomm_pn *pn = (void *) skb->data; |
struct rfcomm_dlc *d; |
u8 dlci = pn->dlci; |
|
BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); |
|
if (!dlci) |
return 0; |
|
d = rfcomm_dlc_get(s, dlci); |
if (d) { |
if (cr) { |
/* PN request */ |
rfcomm_apply_pn(d, cr, pn); |
rfcomm_send_pn(s, 0, d); |
} else { |
/* PN response */ |
switch (d->state) { |
case BT_CONFIG: |
rfcomm_apply_pn(d, cr, pn); |
|
d->state = BT_CONNECT; |
rfcomm_send_sabm(s, d->dlci); |
break; |
} |
} |
} else { |
u8 channel = __srv_channel(dlci); |
|
if (!cr) |
return 0; |
|
/* PN request for non existing DLC. |
* Assume incomming connection. */ |
if (rfcomm_connect_ind(s, channel, &d)) { |
d->dlci = dlci; |
d->addr = __addr(s->initiator, dlci); |
rfcomm_dlc_link(s, d); |
|
rfcomm_apply_pn(d, cr, pn); |
|
d->state = BT_OPEN; |
rfcomm_send_pn(s, 0, d); |
} else { |
rfcomm_send_dm(s, dlci); |
} |
} |
return 0; |
} |
|
static int rfcomm_recv_rpn(struct rfcomm_session *s, int cr, int len, struct sk_buff *skb) |
{ |
struct rfcomm_rpn *rpn = (void *) skb->data; |
u8 dlci = __get_dlci(rpn->dlci); |
|
u8 bit_rate = 0; |
u8 data_bits = 0; |
u8 stop_bits = 0; |
u8 parity = 0; |
u8 flow_ctrl = 0; |
u8 xon_char = 0; |
u8 xoff_char = 0; |
u16 rpn_mask = RFCOMM_RPN_PM_ALL; |
|
BT_DBG("dlci %d cr %d len 0x%x bitr 0x%x line 0x%x flow 0x%x xonc 0x%x xoffc 0x%x pm 0x%x", |
dlci, cr, len, rpn->bit_rate, rpn->line_settings, rpn->flow_ctrl, |
rpn->xon_char, rpn->xoff_char, rpn->param_mask); |
|
if (!cr) |
return 0; |
|
if (len == 1) { |
/* request: return default setting */ |
bit_rate = RFCOMM_RPN_BR_115200; |
data_bits = RFCOMM_RPN_DATA_8; |
stop_bits = RFCOMM_RPN_STOP_1; |
parity = RFCOMM_RPN_PARITY_NONE; |
flow_ctrl = RFCOMM_RPN_FLOW_NONE; |
xon_char = RFCOMM_RPN_XON_CHAR; |
xoff_char = RFCOMM_RPN_XOFF_CHAR; |
|
goto rpn_out; |
} |
/* check for sane values: ignore/accept bit_rate, 8 bits, 1 stop bit, no parity, |
no flow control lines, normal XON/XOFF chars */ |
if (rpn->param_mask & RFCOMM_RPN_PM_BITRATE) { |
bit_rate = rpn->bit_rate; |
if (bit_rate != RFCOMM_RPN_BR_115200) { |
BT_DBG("RPN bit rate mismatch 0x%x", bit_rate); |
bit_rate = RFCOMM_RPN_BR_115200; |
rpn_mask ^= RFCOMM_RPN_PM_BITRATE; |
} |
} |
if (rpn->param_mask & RFCOMM_RPN_PM_DATA) { |
data_bits = __get_rpn_data_bits(rpn->line_settings); |
if (data_bits != RFCOMM_RPN_DATA_8) { |
BT_DBG("RPN data bits mismatch 0x%x", data_bits); |
data_bits = RFCOMM_RPN_DATA_8; |
rpn_mask ^= RFCOMM_RPN_PM_DATA; |
} |
} |
if (rpn->param_mask & RFCOMM_RPN_PM_STOP) { |
stop_bits = __get_rpn_stop_bits(rpn->line_settings); |
if (stop_bits != RFCOMM_RPN_STOP_1) { |
BT_DBG("RPN stop bits mismatch 0x%x", stop_bits); |
stop_bits = RFCOMM_RPN_STOP_1; |
rpn_mask ^= RFCOMM_RPN_PM_STOP; |
} |
} |
if (rpn->param_mask & RFCOMM_RPN_PM_PARITY) { |
parity = __get_rpn_parity(rpn->line_settings); |
if (parity != RFCOMM_RPN_PARITY_NONE) { |
BT_DBG("RPN parity mismatch 0x%x", parity); |
parity = RFCOMM_RPN_PARITY_NONE; |
rpn_mask ^= RFCOMM_RPN_PM_PARITY; |
} |
} |
if (rpn->param_mask & RFCOMM_RPN_PM_FLOW) { |
flow_ctrl = rpn->flow_ctrl; |
if (flow_ctrl != RFCOMM_RPN_FLOW_NONE) { |
BT_DBG("RPN flow ctrl mismatch 0x%x", flow_ctrl); |
flow_ctrl = RFCOMM_RPN_FLOW_NONE; |
rpn_mask ^= RFCOMM_RPN_PM_FLOW; |
} |
} |
if (rpn->param_mask & RFCOMM_RPN_PM_XON) { |
xon_char = rpn->xon_char; |
if (xon_char != RFCOMM_RPN_XON_CHAR) { |
BT_DBG("RPN XON char mismatch 0x%x", xon_char); |
xon_char = RFCOMM_RPN_XON_CHAR; |
rpn_mask ^= RFCOMM_RPN_PM_XON; |
} |
} |
if (rpn->param_mask & RFCOMM_RPN_PM_XOFF) { |
xoff_char = rpn->xoff_char; |
if (xoff_char != RFCOMM_RPN_XOFF_CHAR) { |
BT_DBG("RPN XOFF char mismatch 0x%x", xoff_char); |
xoff_char = RFCOMM_RPN_XOFF_CHAR; |
rpn_mask ^= RFCOMM_RPN_PM_XOFF; |
} |
} |
|
rpn_out: |
rfcomm_send_rpn(s, 0, dlci, |
bit_rate, data_bits, stop_bits, parity, flow_ctrl, |
xon_char, xoff_char, rpn_mask); |
|
return 0; |
} |
|
static int rfcomm_recv_rls(struct rfcomm_session *s, int cr, struct sk_buff *skb) |
{ |
struct rfcomm_rls *rls = (void *) skb->data; |
u8 dlci = __get_dlci(rls->dlci); |
|
BT_DBG("dlci %d cr %d status 0x%x", dlci, cr, rls->status); |
|
if (!cr) |
return 0; |
|
/* FIXME: We should probably do something with this |
information here. But for now it's sufficient just |
to reply -- Bluetooth 1.1 says it's mandatory to |
recognise and respond to RLS */ |
|
rfcomm_send_rls(s, 0, dlci, rls->status); |
|
return 0; |
} |
|
static int rfcomm_recv_msc(struct rfcomm_session *s, int cr, struct sk_buff *skb) |
{ |
struct rfcomm_msc *msc = (void *) skb->data; |
struct rfcomm_dlc *d; |
u8 dlci = __get_dlci(msc->dlci); |
|
BT_DBG("dlci %d cr %d v24 0x%x", dlci, cr, msc->v24_sig); |
|
d = rfcomm_dlc_get(s, dlci); |
if (!d) |
return 0; |
|
if (cr) { |
if (msc->v24_sig & RFCOMM_V24_FC && !d->cfc) |
set_bit(RFCOMM_TX_THROTTLED, &d->flags); |
else |
clear_bit(RFCOMM_TX_THROTTLED, &d->flags); |
|
rfcomm_dlc_lock(d); |
if (d->modem_status) |
d->modem_status(d, msc->v24_sig); |
rfcomm_dlc_unlock(d); |
|
rfcomm_send_msc(s, 0, dlci, msc->v24_sig); |
|
d->mscex |= RFCOMM_MSCEX_RX; |
} else |
d->mscex |= RFCOMM_MSCEX_TX; |
|
return 0; |
} |
|
static int rfcomm_recv_mcc(struct rfcomm_session *s, struct sk_buff *skb) |
{ |
struct rfcomm_mcc *mcc = (void *) skb->data; |
u8 type, cr, len; |
|
cr = __test_cr(mcc->type); |
type = __get_mcc_type(mcc->type); |
len = __get_mcc_len(mcc->len); |
|
BT_DBG("%p type 0x%x cr %d", s, type, cr); |
|
skb_pull(skb, 2); |
|
switch (type) { |
case RFCOMM_PN: |
rfcomm_recv_pn(s, cr, skb); |
break; |
|
case RFCOMM_RPN: |
rfcomm_recv_rpn(s, cr, len, skb); |
break; |
|
case RFCOMM_RLS: |
rfcomm_recv_rls(s, cr, skb); |
break; |
|
case RFCOMM_MSC: |
rfcomm_recv_msc(s, cr, skb); |
break; |
|
case RFCOMM_FCOFF: |
if (cr) { |
set_bit(RFCOMM_TX_THROTTLED, &s->flags); |
rfcomm_send_fcoff(s, 0); |
} |
break; |
|
case RFCOMM_FCON: |
if (cr) { |
clear_bit(RFCOMM_TX_THROTTLED, &s->flags); |
rfcomm_send_fcon(s, 0); |
} |
break; |
|
case RFCOMM_TEST: |
if (cr) |
rfcomm_send_test(s, 0, skb->data, skb->len); |
break; |
|
case RFCOMM_NSC: |
break; |
|
default: |
BT_ERR("Unknown control type 0x%02x", type); |
rfcomm_send_nsc(s, cr, type); |
break; |
} |
return 0; |
} |
|
static int rfcomm_recv_data(struct rfcomm_session *s, u8 dlci, int pf, struct sk_buff *skb) |
{ |
struct rfcomm_dlc *d; |
|
BT_DBG("session %p state %ld dlci %d pf %d", s, s->state, dlci, pf); |
|
d = rfcomm_dlc_get(s, dlci); |
if (!d) { |
rfcomm_send_dm(s, dlci); |
goto drop; |
} |
|
if (pf && d->cfc) { |
u8 credits = *(u8 *) skb->data; skb_pull(skb, 1); |
|
d->tx_credits += credits; |
if (d->tx_credits) |
clear_bit(RFCOMM_TX_THROTTLED, &d->flags); |
} |
|
if (skb->len && d->state == BT_CONNECTED) { |
rfcomm_dlc_lock(d); |
d->rx_credits--; |
d->data_ready(d, skb); |
rfcomm_dlc_unlock(d); |
return 0; |
} |
|
drop: |
kfree_skb(skb); |
return 0; |
} |
|
static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb) |
{ |
struct rfcomm_hdr *hdr = (void *) skb->data; |
u8 type, dlci, fcs; |
|
dlci = __get_dlci(hdr->addr); |
type = __get_type(hdr->ctrl); |
|
/* Trim FCS */ |
skb->len--; skb->tail--; |
fcs = *(u8 *) skb->tail; |
|
if (__check_fcs(skb->data, type, fcs)) { |
BT_ERR("bad checksum in packet"); |
kfree_skb(skb); |
return -EILSEQ; |
} |
|
if (__test_ea(hdr->len)) |
skb_pull(skb, 3); |
else |
skb_pull(skb, 4); |
|
switch (type) { |
case RFCOMM_SABM: |
if (__test_pf(hdr->ctrl)) |
rfcomm_recv_sabm(s, dlci); |
break; |
|
case RFCOMM_DISC: |
if (__test_pf(hdr->ctrl)) |
rfcomm_recv_disc(s, dlci); |
break; |
|
case RFCOMM_UA: |
if (__test_pf(hdr->ctrl)) |
rfcomm_recv_ua(s, dlci); |
break; |
|
case RFCOMM_DM: |
rfcomm_recv_dm(s, dlci); |
break; |
|
case RFCOMM_UIH: |
if (dlci) |
return rfcomm_recv_data(s, dlci, __test_pf(hdr->ctrl), skb); |
|
rfcomm_recv_mcc(s, skb); |
break; |
|
default: |
BT_ERR("Unknown packet type 0x%02x\n", type); |
break; |
} |
kfree_skb(skb); |
return 0; |
} |
|
/* ---- Connection and data processing ---- */ |
|
static void rfcomm_process_connect(struct rfcomm_session *s) |
{ |
struct rfcomm_dlc *d; |
struct list_head *p, *n; |
|
BT_DBG("session %p state %ld", s, s->state); |
|
list_for_each_safe(p, n, &s->dlcs) { |
d = list_entry(p, struct rfcomm_dlc, list); |
if (d->state == BT_CONFIG) { |
d->mtu = s->mtu; |
rfcomm_send_pn(s, 1, d); |
} |
} |
} |
|
/* Send data queued for the DLC. |
* Return number of frames left in the queue. |
*/ |
static inline int rfcomm_process_tx(struct rfcomm_dlc *d) |
{ |
struct sk_buff *skb; |
int err; |
|
BT_DBG("dlc %p state %ld cfc %d rx_credits %d tx_credits %d", |
d, d->state, d->cfc, d->rx_credits, d->tx_credits); |
|
/* Send pending MSC */ |
if (test_and_clear_bit(RFCOMM_MSC_PENDING, &d->flags)) |
rfcomm_send_msc(d->session, 1, d->dlci, d->v24_sig); |
|
if (d->cfc) { |
/* CFC enabled. |
* Give them some credits */ |
if (!test_bit(RFCOMM_RX_THROTTLED, &d->flags) && |
d->rx_credits <= (d->cfc >> 2)) { |
rfcomm_send_credits(d->session, d->addr, d->cfc - d->rx_credits); |
d->rx_credits = d->cfc; |
} |
} else { |
/* CFC disabled. |
* Give ourselves some credits */ |
d->tx_credits = 5; |
} |
|
if (test_bit(RFCOMM_TX_THROTTLED, &d->flags)) |
return skb_queue_len(&d->tx_queue); |
|
while (d->tx_credits && (skb = skb_dequeue(&d->tx_queue))) { |
err = rfcomm_send_frame(d->session, skb->data, skb->len); |
if (err < 0) { |
skb_queue_head(&d->tx_queue, skb); |
break; |
} |
kfree_skb(skb); |
d->tx_credits--; |
} |
|
if (d->cfc && !d->tx_credits) { |
/* We're out of TX credits. |
* Set TX_THROTTLED flag to avoid unnesary wakeups by dlc_send. */ |
set_bit(RFCOMM_TX_THROTTLED, &d->flags); |
} |
|
return skb_queue_len(&d->tx_queue); |
} |
|
static inline void rfcomm_process_dlcs(struct rfcomm_session *s) |
{ |
struct rfcomm_dlc *d; |
struct list_head *p, *n; |
|
BT_DBG("session %p state %ld", s, s->state); |
|
list_for_each_safe(p, n, &s->dlcs) { |
d = list_entry(p, struct rfcomm_dlc, list); |
if (test_bit(RFCOMM_TIMED_OUT, &d->flags)) { |
__rfcomm_dlc_close(d, ETIMEDOUT); |
continue; |
} |
|
if (test_bit(RFCOMM_TX_THROTTLED, &s->flags)) |
continue; |
|
if ((d->state == BT_CONNECTED || d->state == BT_DISCONN) && |
d->mscex == RFCOMM_MSCEX_OK) |
rfcomm_process_tx(d); |
} |
} |
|
static inline void rfcomm_process_rx(struct rfcomm_session *s) |
{ |
struct socket *sock = s->sock; |
struct sock *sk = sock->sk; |
struct sk_buff *skb; |
|
BT_DBG("session %p state %ld qlen %d", s, s->state, skb_queue_len(&sk->receive_queue)); |
|
/* Get data directly from socket receive queue without copying it. */ |
while ((skb = skb_dequeue(&sk->receive_queue))) { |
skb_orphan(skb); |
rfcomm_recv_frame(s, skb); |
} |
|
if (sk->state == BT_CLOSED) { |
if (!s->initiator) |
rfcomm_session_put(s); |
|
rfcomm_session_close(s, sk->err); |
} |
} |
|
static inline void rfcomm_accept_connection(struct rfcomm_session *s) |
{ |
struct socket *sock = s->sock, *nsock; |
int err; |
|
/* Fast check for a new connection. |
* Avoids unnesesary socket allocations. */ |
if (list_empty(&bluez_pi(sock->sk)->accept_q)) |
return; |
|
BT_DBG("session %p", s); |
|
nsock = sock_alloc(); |
if (!nsock) |
return; |
|
nsock->type = sock->type; |
nsock->ops = sock->ops; |
|
err = sock->ops->accept(sock, nsock, O_NONBLOCK); |
if (err < 0) { |
sock_release(nsock); |
return; |
} |
|
/* Set our callbacks */ |
nsock->sk->data_ready = rfcomm_l2data_ready; |
nsock->sk->state_change = rfcomm_l2state_change; |
|
s = rfcomm_session_add(nsock, BT_OPEN); |
if (s) { |
rfcomm_session_hold(s); |
rfcomm_schedule(RFCOMM_SCHED_RX); |
} else |
sock_release(nsock); |
} |
|
static inline void rfcomm_check_connection(struct rfcomm_session *s) |
{ |
struct sock *sk = s->sock->sk; |
|
BT_DBG("%p state %ld", s, s->state); |
|
switch(sk->state) { |
case BT_CONNECTED: |
s->state = BT_CONNECT; |
|
/* We can adjust MTU on outgoing sessions. |
* L2CAP MTU minus UIH header and FCS. */ |
s->mtu = min(l2cap_pi(sk)->omtu, l2cap_pi(sk)->imtu) - 5; |
|
rfcomm_send_sabm(s, 0); |
break; |
|
case BT_CLOSED: |
s->state = BT_CLOSED; |
rfcomm_session_close(s, sk->err); |
break; |
} |
} |
|
static inline void rfcomm_process_sessions(void) |
{ |
struct list_head *p, *n; |
|
rfcomm_lock(); |
|
list_for_each_safe(p, n, &session_list) { |
struct rfcomm_session *s; |
s = list_entry(p, struct rfcomm_session, list); |
|
if (s->state == BT_LISTEN) { |
rfcomm_accept_connection(s); |
continue; |
} |
|
rfcomm_session_hold(s); |
|
switch (s->state) { |
case BT_BOUND: |
rfcomm_check_connection(s); |
break; |
|
default: |
rfcomm_process_rx(s); |
break; |
} |
|
rfcomm_process_dlcs(s); |
|
rfcomm_session_put(s); |
} |
|
rfcomm_unlock(); |
} |
|
static void rfcomm_worker(void) |
{ |
BT_DBG(""); |
|
daemonize(); reparent_to_init(); |
set_fs(KERNEL_DS); |
|
while (!atomic_read(&terminate)) { |
BT_DBG("worker loop event 0x%lx", rfcomm_event); |
|
if (!test_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event)) { |
/* No pending events. Let's sleep. |
* Incomming connections and data will wake us up. */ |
set_current_state(TASK_INTERRUPTIBLE); |
schedule(); |
} |
|
/* Process stuff */ |
clear_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event); |
rfcomm_process_sessions(); |
} |
set_current_state(TASK_RUNNING); |
return; |
} |
|
static int rfcomm_add_listener(bdaddr_t *ba) |
{ |
struct sockaddr_l2 addr; |
struct l2cap_options opts; |
struct socket *sock; |
struct rfcomm_session *s; |
int size, err = 0; |
|
/* Create socket */ |
err = rfcomm_l2sock_create(&sock); |
if (err < 0) { |
BT_ERR("Create socket failed %d", err); |
return err; |
} |
|
/* Bind socket */ |
bacpy(&addr.l2_bdaddr, ba); |
addr.l2_family = AF_BLUETOOTH; |
addr.l2_psm = htobs(RFCOMM_PSM); |
err = sock->ops->bind(sock, (struct sockaddr *) &addr, sizeof(addr)); |
if (err < 0) { |
BT_ERR("Bind failed %d", err); |
goto failed; |
} |
|
/* Set L2CAP options */ |
size = sizeof(opts); |
sock->ops->getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, &size); |
|
opts.imtu = RFCOMM_MAX_L2CAP_MTU; |
sock->ops->setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, size); |
|
/* Start listening on the socket */ |
err = sock->ops->listen(sock, 10); |
if (err) { |
BT_ERR("Listen failed %d", err); |
goto failed; |
} |
|
/* Add listening session */ |
s = rfcomm_session_add(sock, BT_LISTEN); |
if (!s) |
goto failed; |
|
rfcomm_session_hold(s); |
return 0; |
failed: |
sock_release(sock); |
return err; |
} |
|
static void rfcomm_kill_listener(void) |
{ |
struct rfcomm_session *s; |
struct list_head *p, *n; |
|
BT_DBG(""); |
|
list_for_each_safe(p, n, &session_list) { |
s = list_entry(p, struct rfcomm_session, list); |
rfcomm_session_del(s); |
} |
} |
|
static int rfcomm_run(void *unused) |
{ |
rfcomm_thread = current; |
|
atomic_inc(&running); |
|
daemonize(); reparent_to_init(); |
|
sigfillset(¤t->blocked); |
set_fs(KERNEL_DS); |
|
sprintf(current->comm, "krfcommd"); |
|
BT_DBG(""); |
|
rfcomm_add_listener(BDADDR_ANY); |
|
rfcomm_worker(); |
|
rfcomm_kill_listener(); |
|
atomic_dec(&running); |
return 0; |
} |
|
/* ---- Proc fs support ---- */ |
static int rfcomm_dlc_dump(char *buf) |
{ |
struct rfcomm_session *s; |
struct sock *sk; |
struct list_head *p, *pp; |
char *ptr = buf; |
|
rfcomm_lock(); |
|
list_for_each(p, &session_list) { |
s = list_entry(p, struct rfcomm_session, list); |
sk = s->sock->sk; |
|
list_for_each(pp, &s->dlcs) { |
struct rfcomm_dlc *d; |
d = list_entry(pp, struct rfcomm_dlc, list); |
|
ptr += sprintf(ptr, "dlc %s %s %ld %d %d %d %d\n", |
batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst), |
d->state, d->dlci, d->mtu, d->rx_credits, d->tx_credits); |
} |
} |
|
rfcomm_unlock(); |
|
return ptr - buf; |
} |
|
extern int rfcomm_sock_dump(char *buf); |
|
static int rfcomm_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv) |
{ |
char *ptr = buf; |
int len; |
|
BT_DBG("count %d, offset %ld", count, offset); |
|
ptr += rfcomm_dlc_dump(ptr); |
ptr += rfcomm_sock_dump(ptr); |
len = ptr - buf; |
|
if (len <= count + offset) |
*eof = 1; |
|
*start = buf + offset; |
len -= offset; |
|
if (len > count) |
len = count; |
if (len < 0) |
len = 0; |
|
return len; |
} |
|
/* ---- Initialization ---- */ |
int __init rfcomm_init(void) |
{ |
l2cap_load(); |
|
kernel_thread(rfcomm_run, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); |
|
rfcomm_init_sockets(); |
|
#ifdef CONFIG_BLUEZ_RFCOMM_TTY |
rfcomm_init_ttys(); |
#endif |
|
create_proc_read_entry("bluetooth/rfcomm", 0, 0, rfcomm_read_proc, NULL); |
|
BT_INFO("BlueZ RFCOMM ver %s", VERSION); |
BT_INFO("Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>"); |
BT_INFO("Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org>"); |
return 0; |
} |
|
void rfcomm_cleanup(void) |
{ |
/* Terminate working thread. |
* ie. Set terminate flag and wake it up */ |
atomic_inc(&terminate); |
rfcomm_schedule(RFCOMM_SCHED_STATE); |
|
/* Wait until thread is running */ |
while (atomic_read(&running)) |
schedule(); |
|
remove_proc_entry("bluetooth/rfcomm", NULL); |
|
#ifdef CONFIG_BLUEZ_RFCOMM_TTY |
rfcomm_cleanup_ttys(); |
#endif |
|
rfcomm_cleanup_sockets(); |
return; |
} |
|
module_init(rfcomm_init); |
module_exit(rfcomm_cleanup); |
|
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>"); |
MODULE_DESCRIPTION("BlueZ RFCOMM ver " VERSION); |
MODULE_LICENSE("GPL"); |
/rfcomm/Config.in
0,0 → 1,10
# |
# Bluetooth RFCOMM layer configuration |
# |
|
dep_tristate 'RFCOMM protocol support' CONFIG_BLUEZ_RFCOMM $CONFIG_BLUEZ_L2CAP |
|
if [ "$CONFIG_BLUEZ_RFCOMM" != "n" ]; then |
bool ' RFCOMM TTY support' CONFIG_BLUEZ_RFCOMM_TTY |
fi |
|
/rfcomm/Makefile
0,0 → 1,11
# |
# Makefile for the Linux Bluetooth RFCOMM layer |
# |
|
O_TARGET := rfcomm.o |
|
obj-y := core.o sock.o crc.o |
obj-$(CONFIG_BLUEZ_RFCOMM_TTY) += tty.o |
obj-m += $(O_TARGET) |
|
include $(TOPDIR)/Rules.make |
/bnep/sock.c
0,0 → 1,210
/* |
BNEP implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2001-2002 Inventel Systemes |
Written 2001-2002 by |
David Libault <david.libault@inventel.fr> |
|
Copyright (C) 2002 Maxim Krasnyanskiy <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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* $Id: sock.c,v 1.1.1.1 2004-04-15 01:17:08 phoenix Exp $ |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/skbuff.h> |
#include <linux/socket.h> |
#include <linux/ioctl.h> |
#include <linux/file.h> |
#include <net/sock.h> |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
|
#include "bnep.h" |
|
#ifndef CONFIG_BLUEZ_BNEP_DEBUG |
#undef BT_DBG |
#define BT_DBG( A... ) |
#endif |
|
static int bnep_sock_release(struct socket *sock) |
{ |
struct sock *sk = sock->sk; |
|
BT_DBG("sock %p sk %p", sock, sk); |
|
if (!sk) |
return 0; |
|
sock_orphan(sk); |
sock_put(sk); |
|
MOD_DEC_USE_COUNT; |
return 0; |
} |
|
static int bnep_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) |
{ |
struct bnep_connlist_req cl; |
struct bnep_connadd_req ca; |
struct bnep_conndel_req cd; |
struct bnep_conninfo ci; |
struct socket *nsock; |
int err; |
|
BT_DBG("cmd %x arg %lx", cmd, arg); |
|
switch (cmd) { |
case BNEPCONNADD: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
|
if (copy_from_user(&ca, (void *) arg, sizeof(ca))) |
return -EFAULT; |
|
nsock = sockfd_lookup(ca.sock, &err); |
if (!nsock) |
return err; |
|
if (nsock->sk->state != BT_CONNECTED) { |
fput(nsock->file); |
return -EBADFD; |
} |
|
err = bnep_add_connection(&ca, nsock); |
if (!err) { |
if (copy_to_user((void *) arg, &ca, sizeof(ca))) |
err = -EFAULT; |
} else |
fput(nsock->file); |
|
return err; |
|
case BNEPCONNDEL: |
if (!capable(CAP_NET_ADMIN)) |
return -EACCES; |
|
if (copy_from_user(&cd, (void *) arg, sizeof(cd))) |
return -EFAULT; |
|
return bnep_del_connection(&cd); |
|
case BNEPGETCONNLIST: |
if (copy_from_user(&cl, (void *) arg, sizeof(cl))) |
return -EFAULT; |
|
if (cl.cnum <= 0) |
return -EINVAL; |
|
err = bnep_get_connlist(&cl); |
if (!err && copy_to_user((void *) arg, &cl, sizeof(cl))) |
return -EFAULT; |
|
return err; |
|
case BNEPGETCONNINFO: |
if (copy_from_user(&ci, (void *) arg, sizeof(ci))) |
return -EFAULT; |
|
err = bnep_get_conninfo(&ci); |
if (!err && copy_to_user((void *) arg, &ci, sizeof(ci))) |
return -EFAULT; |
|
return err; |
|
default: |
return -EINVAL; |
} |
|
return 0; |
} |
|
static struct proto_ops bnep_sock_ops = { |
family: PF_BLUETOOTH, |
release: bnep_sock_release, |
ioctl: bnep_sock_ioctl, |
bind: sock_no_bind, |
getname: sock_no_getname, |
sendmsg: sock_no_sendmsg, |
recvmsg: sock_no_recvmsg, |
poll: sock_no_poll, |
listen: sock_no_listen, |
shutdown: sock_no_shutdown, |
setsockopt: sock_no_setsockopt, |
getsockopt: sock_no_getsockopt, |
connect: sock_no_connect, |
socketpair: sock_no_socketpair, |
accept: sock_no_accept, |
mmap: sock_no_mmap |
}; |
|
static int bnep_sock_create(struct socket *sock, int protocol) |
{ |
struct sock *sk; |
|
BT_DBG("sock %p", sock); |
|
if (sock->type != SOCK_RAW) |
return -ESOCKTNOSUPPORT; |
|
sock->ops = &bnep_sock_ops; |
|
if (!(sk = sk_alloc(PF_BLUETOOTH, GFP_KERNEL, 1))) |
return -ENOMEM; |
|
MOD_INC_USE_COUNT; |
|
sock->state = SS_UNCONNECTED; |
sock_init_data(sock, sk); |
|
sk->destruct = NULL; |
sk->protocol = protocol; |
|
return 0; |
} |
|
static struct net_proto_family bnep_sock_family_ops = { |
family: PF_BLUETOOTH, |
create: bnep_sock_create |
}; |
|
int bnep_sock_init(void) |
{ |
bluez_sock_register(BTPROTO_BNEP, &bnep_sock_family_ops); |
return 0; |
} |
|
int bnep_sock_cleanup(void) |
{ |
if (bluez_sock_unregister(BTPROTO_BNEP)) |
BT_ERR("Can't unregister BNEP socket"); |
return 0; |
} |
/bnep/Makefile.lib
0,0 → 1,210
obj-$(CONFIG_BLUEZ_BNEP) += crc32.o |
/bnep/netdev.c
0,0 → 1,254
/* |
BNEP implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2001-2002 Inventel Systemes |
Written 2001-2002 by |
|
David Libault <david.libault@inventel.fr> |
|
Copyright (C) 2002 Maxim Krasnyanskiy <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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* $Id: netdev.c,v 1.1.1.1 2004-04-15 01:17:08 phoenix Exp $ |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/socket.h> |
#include <linux/netdevice.h> |
#include <linux/etherdevice.h> |
#include <linux/skbuff.h> |
#include <linux/wait.h> |
|
#include <asm/unaligned.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/hci_core.h> |
#include <net/bluetooth/l2cap.h> |
|
#include "bnep.h" |
|
#ifndef CONFIG_BLUEZ_BNEP_DEBUG |
#undef BT_DBG |
#define BT_DBG( A... ) |
#endif |
|
#define BNEP_TX_QUEUE_LEN 20 |
|
static int bnep_net_open(struct net_device *dev) |
{ |
netif_start_queue(dev); |
return 0; |
} |
|
static int bnep_net_close(struct net_device *dev) |
{ |
netif_stop_queue(dev); |
return 0; |
} |
|
static struct net_device_stats *bnep_net_get_stats(struct net_device *dev) |
{ |
struct bnep_session *s = dev->priv; |
return &s->stats; |
} |
|
static void bnep_net_set_mc_list(struct net_device *dev) |
{ |
#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER |
struct bnep_session *s = dev->priv; |
struct sock *sk = s->sock->sk; |
struct bnep_set_filter_req *r; |
struct sk_buff *skb; |
int size; |
|
BT_DBG("%s mc_count %d", dev->name, dev->mc_count); |
|
size = sizeof(*r) + (BNEP_MAX_MULTICAST_FILTERS + 1) * ETH_ALEN * 2; |
skb = alloc_skb(size, GFP_ATOMIC); |
if (!skb) { |
BT_ERR("%s Multicast list allocation failed", dev->name); |
return; |
} |
|
r = (void *) skb->data; |
__skb_put(skb, sizeof(*r)); |
|
r->type = BNEP_CONTROL; |
r->ctrl = BNEP_FILTER_MULTI_ADDR_SET; |
|
if (dev->flags & (IFF_PROMISC | IFF_ALLMULTI)) { |
u8 start[ETH_ALEN] = { 0x01 }; |
|
/* Request all addresses */ |
memcpy(__skb_put(skb, ETH_ALEN), start, ETH_ALEN); |
memcpy(__skb_put(skb, ETH_ALEN), dev->broadcast, ETH_ALEN); |
r->len = htons(ETH_ALEN * 2); |
} else { |
struct dev_mc_list *dmi = dev->mc_list; |
int i, len = skb->len; |
|
if (dev->flags & IFF_BROADCAST) { |
memcpy(__skb_put(skb, ETH_ALEN), dev->broadcast, ETH_ALEN); |
memcpy(__skb_put(skb, ETH_ALEN), dev->broadcast, ETH_ALEN); |
} |
|
/* FIXME: We should group addresses here. */ |
|
for (i = 0; i < dev->mc_count && i < BNEP_MAX_MULTICAST_FILTERS; i++) { |
memcpy(__skb_put(skb, ETH_ALEN), dmi->dmi_addr, ETH_ALEN); |
memcpy(__skb_put(skb, ETH_ALEN), dmi->dmi_addr, ETH_ALEN); |
dmi = dmi->next; |
} |
r->len = htons(skb->len - len); |
} |
|
skb_queue_tail(&sk->write_queue, skb); |
wake_up_interruptible(sk->sleep); |
#endif |
} |
|
static int bnep_net_set_mac_addr(struct net_device *dev, void *arg) |
{ |
BT_DBG("%s", dev->name); |
return 0; |
} |
|
static void bnep_net_timeout(struct net_device *dev) |
{ |
BT_DBG("net_timeout"); |
netif_wake_queue(dev); |
} |
|
static int bnep_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) |
{ |
return -EINVAL; |
} |
|
#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER |
static inline int bnep_net_mc_filter(struct sk_buff *skb, struct bnep_session *s) |
{ |
struct ethhdr *eh = (void *) skb->data; |
|
if ((eh->h_dest[0] & 1) && !test_bit(bnep_mc_hash(eh->h_dest), &s->mc_filter)) { |
BT_DBG("BNEP: filtered skb %p, dst %.2x:%.2x:%.2x:%.2x:%.2x:%.2x", skb, |
eh->h_dest[0], eh->h_dest[1], eh->h_dest[2], |
eh->h_dest[3], eh->h_dest[4], eh->h_dest[5]); |
return 1; |
} |
return 0; |
} |
#endif |
|
#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER |
/* Determine ether protocol. Based on eth_type_trans. */ |
static inline u16 bnep_net_eth_proto(struct sk_buff *skb) |
{ |
struct ethhdr *eh = (void *) skb->data; |
|
if (ntohs(eh->h_proto) >= 1536) |
return eh->h_proto; |
|
if (get_unaligned((u16 *) skb->data) == 0xFFFF) |
return htons(ETH_P_802_3); |
|
return htons(ETH_P_802_2); |
} |
|
static inline int bnep_net_proto_filter(struct sk_buff *skb, struct bnep_session *s) |
{ |
u16 proto = bnep_net_eth_proto(skb); |
struct bnep_proto_filter *f = s->proto_filter; |
int i; |
|
for (i = 0; i < BNEP_MAX_PROTO_FILTERS && f[i].end; i++) { |
if (proto >= f[i].start && proto <= f[i].end) |
return 0; |
} |
|
BT_DBG("BNEP: filtered skb %p, proto 0x%.4x", skb, proto); |
return 1; |
} |
#endif |
|
static int bnep_net_xmit(struct sk_buff *skb, struct net_device *dev) |
{ |
struct bnep_session *s = dev->priv; |
struct sock *sk = s->sock->sk; |
|
BT_DBG("skb %p, dev %p", skb, dev); |
|
#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER |
if (bnep_net_mc_filter(skb, s)) { |
kfree_skb(skb); |
return 0; |
} |
#endif |
|
#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER |
if (bnep_net_proto_filter(skb, s)) { |
kfree_skb(skb); |
return 0; |
} |
#endif |
|
/* |
* We cannot send L2CAP packets from here as we are potentially in a bh. |
* So we have to queue them and wake up session thread which is sleeping |
* on the sk->sleep. |
*/ |
dev->trans_start = jiffies; |
skb_queue_tail(&sk->write_queue, skb); |
wake_up_interruptible(sk->sleep); |
|
if (skb_queue_len(&sk->write_queue) >= BNEP_TX_QUEUE_LEN) { |
BT_DBG("tx queue is full"); |
|
/* Stop queuing. |
* Session thread will do netif_wake_queue() */ |
netif_stop_queue(dev); |
} |
|
return 0; |
} |
|
int bnep_net_init(struct net_device *dev) |
{ |
struct bnep_session *s = dev->priv; |
|
memcpy(dev->dev_addr, s->eh.h_dest, ETH_ALEN); |
dev->addr_len = ETH_ALEN; |
|
ether_setup(dev); |
|
dev->open = bnep_net_open; |
dev->stop = bnep_net_close; |
dev->hard_start_xmit = bnep_net_xmit; |
dev->get_stats = bnep_net_get_stats; |
dev->do_ioctl = bnep_net_ioctl; |
dev->set_mac_address = bnep_net_set_mac_addr; |
dev->set_multicast_list = bnep_net_set_mc_list; |
|
dev->watchdog_timeo = HZ * 2; |
dev->tx_timeout = bnep_net_timeout; |
|
return 0; |
} |
/bnep/core.c
0,0 → 1,706
/* |
BNEP implementation for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2001-2002 Inventel Systemes |
Written 2001-2002 by |
|
David Libault <david.libault@inventel.fr> |
|
Copyright (C) 2002 Maxim Krasnyanskiy <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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* $Id: core.c,v 1.1.1.1 2004-04-15 01:17:08 phoenix Exp $ |
*/ |
|
#define __KERNEL_SYSCALLS__ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/kernel.h> |
#include <linux/sched.h> |
#include <linux/signal.h> |
#include <linux/init.h> |
#include <linux/wait.h> |
#include <linux/errno.h> |
#include <linux/smp_lock.h> |
#include <linux/net.h> |
#include <net/sock.h> |
|
#include <linux/socket.h> |
#include <linux/file.h> |
|
#include <linux/netdevice.h> |
#include <linux/etherdevice.h> |
#include <linux/skbuff.h> |
|
#include <asm/unaligned.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/l2cap.h> |
|
#include "bnep.h" |
|
#ifndef CONFIG_BLUEZ_BNEP_DEBUG |
#undef BT_DBG |
#define BT_DBG(D...) |
#endif |
|
#define VERSION "1.1" |
|
static LIST_HEAD(bnep_session_list); |
static DECLARE_RWSEM(bnep_session_sem); |
|
static struct bnep_session *__bnep_get_session(u8 *dst) |
{ |
struct bnep_session *s; |
struct list_head *p; |
|
BT_DBG(""); |
|
list_for_each(p, &bnep_session_list) { |
s = list_entry(p, struct bnep_session, list); |
if (!memcmp(dst, s->eh.h_source, ETH_ALEN)) |
return s; |
} |
return NULL; |
} |
|
static void __bnep_link_session(struct bnep_session *s) |
{ |
MOD_INC_USE_COUNT; |
list_add(&s->list, &bnep_session_list); |
} |
|
static void __bnep_unlink_session(struct bnep_session *s) |
{ |
list_del(&s->list); |
MOD_DEC_USE_COUNT; |
} |
|
static int bnep_send(struct bnep_session *s, void *data, size_t len) |
{ |
struct socket *sock = s->sock; |
struct iovec iv = { data, len }; |
s->msg.msg_iov = &iv; |
s->msg.msg_iovlen = 1; |
return sock->ops->sendmsg(sock, &s->msg, len, NULL); |
} |
|
static int bnep_send_rsp(struct bnep_session *s, u8 ctrl, u16 resp) |
{ |
struct bnep_control_rsp rsp; |
rsp.type = BNEP_CONTROL; |
rsp.ctrl = ctrl; |
rsp.resp = htons(resp); |
return bnep_send(s, &rsp, sizeof(rsp)); |
} |
|
static int bnep_ctrl_set_netfilter(struct bnep_session *s, u16 *data, int len) |
{ |
int n; |
|
if (len < 2) |
return -EILSEQ; |
|
n = ntohs(get_unaligned(data)); |
data++; len -= 2; |
|
if (len < n) |
return -EILSEQ; |
|
BT_DBG("filter len %d", n); |
|
#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER |
n /= 4; |
if (n <= BNEP_MAX_PROTO_FILTERS) { |
struct bnep_proto_filter *f = s->proto_filter; |
int i; |
|
for (i = 0; i < n; i++) { |
f[i].start = get_unaligned(data++); |
f[i].end = get_unaligned(data++); |
|
BT_DBG("proto filter start %d end %d", |
f[i].start, f[i].end); |
} |
if (i < BNEP_MAX_PROTO_FILTERS) |
memset(f + i, 0, sizeof(*f)); |
|
bnep_send_rsp(s, BNEP_FILTER_NET_TYPE_RSP, BNEP_SUCCESS); |
} else { |
bnep_send_rsp(s, BNEP_FILTER_NET_TYPE_RSP, BNEP_FILTER_LIMIT_REACHED); |
} |
#else |
bnep_send_rsp(s, BNEP_FILTER_NET_TYPE_RSP, BNEP_FILTER_UNSUPPORTED_REQ); |
#endif |
return 0; |
} |
|
static int bnep_ctrl_set_mcfilter(struct bnep_session *s, u8 *data, int len) |
{ |
int n; |
|
if (len < 2) |
return -EILSEQ; |
|
n = ntohs(get_unaligned((u16 *) data)); |
data += 2; len -= 2; |
|
if (len < n) |
return -EILSEQ; |
|
BT_DBG("filter len %d", n); |
|
#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER |
n /= (ETH_ALEN * 2); |
|
if (n > 0) { |
s->mc_filter = 0; |
|
/* Always send broadcast */ |
set_bit(bnep_mc_hash(s->dev.broadcast), &s->mc_filter); |
|
/* Add address ranges to the multicast hash */ |
for (; n > 0; n--) { |
u8 a1[6], *a2; |
|
memcpy(a1, data, ETH_ALEN); data += ETH_ALEN; |
a2 = data; data += ETH_ALEN; |
|
BT_DBG("mc filter %s -> %s", |
batostr((void *) a1), batostr((void *) a2)); |
|
#define INCA(a) { int i = 5; while (i >=0 && ++a[i--] == 0); } |
|
/* Iterate from a1 to a2 */ |
set_bit(bnep_mc_hash(a1), &s->mc_filter); |
while (memcmp(a1, a2, 6) < 0 && s->mc_filter != ~0LL) { |
INCA(a1); |
set_bit(bnep_mc_hash(a1), &s->mc_filter); |
} |
} |
} |
|
BT_DBG("mc filter hash 0x%llx", s->mc_filter); |
|
bnep_send_rsp(s, BNEP_FILTER_MULTI_ADDR_RSP, BNEP_SUCCESS); |
#else |
bnep_send_rsp(s, BNEP_FILTER_MULTI_ADDR_RSP, BNEP_FILTER_UNSUPPORTED_REQ); |
#endif |
return 0; |
} |
|
static int bnep_rx_control(struct bnep_session *s, void *data, int len) |
{ |
u8 cmd = *(u8 *)data; |
int err = 0; |
|
data++; len--; |
|
switch (cmd) { |
case BNEP_CMD_NOT_UNDERSTOOD: |
case BNEP_SETUP_CONN_REQ: |
case BNEP_SETUP_CONN_RSP: |
case BNEP_FILTER_NET_TYPE_RSP: |
case BNEP_FILTER_MULTI_ADDR_RSP: |
/* Ignore these for now */ |
break; |
|
case BNEP_FILTER_NET_TYPE_SET: |
err = bnep_ctrl_set_netfilter(s, data, len); |
break; |
|
case BNEP_FILTER_MULTI_ADDR_SET: |
err = bnep_ctrl_set_mcfilter(s, data, len); |
break; |
|
default: { |
u8 pkt[3]; |
pkt[0] = BNEP_CONTROL; |
pkt[1] = BNEP_CMD_NOT_UNDERSTOOD; |
pkt[2] = cmd; |
bnep_send(s, pkt, sizeof(pkt)); |
} |
break; |
} |
|
return err; |
} |
|
static int bnep_rx_extension(struct bnep_session *s, struct sk_buff *skb) |
{ |
struct bnep_ext_hdr *h; |
int err = 0; |
|
do { |
h = (void *) skb->data; |
if (!skb_pull(skb, sizeof(*h))) { |
err = -EILSEQ; |
break; |
} |
|
BT_DBG("type 0x%x len %d", h->type, h->len); |
|
switch (h->type & BNEP_TYPE_MASK) { |
case BNEP_EXT_CONTROL: |
bnep_rx_control(s, skb->data, skb->len); |
break; |
|
default: |
/* Unknown extension, skip it. */ |
break; |
} |
|
if (!skb_pull(skb, h->len)) { |
err = -EILSEQ; |
break; |
} |
} while (!err && (h->type & BNEP_EXT_HEADER)); |
|
return err; |
} |
|
static u8 __bnep_rx_hlen[] = { |
ETH_HLEN, /* BNEP_GENERAL */ |
0, /* BNEP_CONTROL */ |
2, /* BNEP_COMPRESSED */ |
ETH_ALEN + 2, /* BNEP_COMPRESSED_SRC_ONLY */ |
ETH_ALEN + 2 /* BNEP_COMPRESSED_DST_ONLY */ |
}; |
#define BNEP_RX_TYPES (sizeof(__bnep_rx_hlen) - 1) |
|
static inline int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb) |
{ |
struct net_device *dev = &s->dev; |
struct sk_buff *nskb; |
u8 type; |
|
dev->last_rx = jiffies; |
s->stats.rx_bytes += skb->len; |
|
type = *(u8 *) skb->data; skb_pull(skb, 1); |
|
if ((type & BNEP_TYPE_MASK) > BNEP_RX_TYPES) |
goto badframe; |
|
if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) { |
bnep_rx_control(s, skb->data, skb->len); |
kfree_skb(skb); |
return 0; |
} |
|
skb->mac.raw = skb->data; |
|
/* Verify and pull out header */ |
if (!skb_pull(skb, __bnep_rx_hlen[type & BNEP_TYPE_MASK])) |
goto badframe; |
|
s->eh.h_proto = get_unaligned((u16 *) (skb->data - 2)); |
|
if (type & BNEP_EXT_HEADER) { |
if (bnep_rx_extension(s, skb) < 0) |
goto badframe; |
} |
|
/* Strip 802.1p header */ |
if (ntohs(s->eh.h_proto) == 0x8100) { |
if (!skb_pull(skb, 4)) |
goto badframe; |
s->eh.h_proto = get_unaligned((u16 *) (skb->data - 2)); |
} |
|
/* We have to alloc new skb and copy data here :(. Because original skb |
* may not be modified and because of the alignment requirements. */ |
nskb = alloc_skb(2 + ETH_HLEN + skb->len, GFP_KERNEL); |
if (!nskb) { |
s->stats.rx_dropped++; |
kfree_skb(skb); |
return -ENOMEM; |
} |
skb_reserve(nskb, 2); |
|
/* Decompress header and construct ether frame */ |
switch (type & BNEP_TYPE_MASK) { |
case BNEP_COMPRESSED: |
memcpy(__skb_put(nskb, ETH_HLEN), &s->eh, ETH_HLEN); |
break; |
|
case BNEP_COMPRESSED_SRC_ONLY: |
memcpy(__skb_put(nskb, ETH_ALEN), s->eh.h_dest, ETH_ALEN); |
memcpy(__skb_put(nskb, ETH_ALEN), skb->mac.raw, ETH_ALEN); |
put_unaligned(s->eh.h_proto, (u16 *) __skb_put(nskb, 2)); |
break; |
|
case BNEP_COMPRESSED_DST_ONLY: |
memcpy(__skb_put(nskb, ETH_ALEN), skb->mac.raw, ETH_ALEN); |
memcpy(__skb_put(nskb, ETH_ALEN + 2), s->eh.h_source, ETH_ALEN + 2); |
break; |
|
case BNEP_GENERAL: |
memcpy(__skb_put(nskb, ETH_ALEN * 2), skb->mac.raw, ETH_ALEN * 2); |
put_unaligned(s->eh.h_proto, (u16 *) __skb_put(nskb, 2)); |
break; |
} |
|
memcpy(__skb_put(nskb, skb->len), skb->data, skb->len); |
kfree_skb(skb); |
|
s->stats.rx_packets++; |
nskb->dev = dev; |
nskb->ip_summed = CHECKSUM_UNNECESSARY; |
nskb->protocol = eth_type_trans(nskb, dev); |
netif_rx_ni(nskb); |
return 0; |
|
badframe: |
s->stats.rx_errors++; |
kfree_skb(skb); |
return 0; |
} |
|
static u8 __bnep_tx_types[] = { |
BNEP_GENERAL, |
BNEP_COMPRESSED_SRC_ONLY, |
BNEP_COMPRESSED_DST_ONLY, |
BNEP_COMPRESSED |
}; |
|
static inline int bnep_tx_frame(struct bnep_session *s, struct sk_buff *skb) |
{ |
struct ethhdr *eh = (void *) skb->data; |
struct socket *sock = s->sock; |
struct iovec iv[3]; |
int len = 0, il = 0; |
u8 type = 0; |
|
BT_DBG("skb %p dev %p type %d", skb, skb->dev, skb->pkt_type); |
|
if (!skb->dev) { |
/* Control frame sent by us */ |
goto send; |
} |
|
iv[il++] = (struct iovec) { &type, 1 }; |
len++; |
|
if (!memcmp(eh->h_dest, s->eh.h_source, ETH_ALEN)) |
type |= 0x01; |
|
if (!memcmp(eh->h_source, s->eh.h_dest, ETH_ALEN)) |
type |= 0x02; |
|
if (type) |
skb_pull(skb, ETH_ALEN * 2); |
|
type = __bnep_tx_types[type]; |
switch (type) { |
case BNEP_COMPRESSED_SRC_ONLY: |
iv[il++] = (struct iovec) { eh->h_source, ETH_ALEN }; |
len += ETH_ALEN; |
break; |
|
case BNEP_COMPRESSED_DST_ONLY: |
iv[il++] = (struct iovec) { eh->h_dest, ETH_ALEN }; |
len += ETH_ALEN; |
break; |
} |
|
send: |
iv[il++] = (struct iovec) { skb->data, skb->len }; |
len += skb->len; |
|
/* FIXME: linearize skb */ |
|
s->msg.msg_iov = iv; |
s->msg.msg_iovlen = il; |
len = sock->ops->sendmsg(sock, &s->msg, len, NULL); |
kfree_skb(skb); |
|
if (len > 0) { |
s->stats.tx_bytes += len; |
s->stats.tx_packets++; |
return 0; |
} |
|
return len; |
} |
|
static int bnep_session(void *arg) |
{ |
struct bnep_session *s = arg; |
struct net_device *dev = &s->dev; |
struct sock *sk = s->sock->sk; |
struct sk_buff *skb; |
wait_queue_t wait; |
|
BT_DBG(""); |
|
daemonize(); reparent_to_init(); |
|
sprintf(current->comm, "kbnepd %s", dev->name); |
|
sigfillset(¤t->blocked); |
flush_signals(current); |
|
current->nice = -15; |
|
set_fs(KERNEL_DS); |
|
init_waitqueue_entry(&wait, current); |
add_wait_queue(sk->sleep, &wait); |
while (!atomic_read(&s->killed)) { |
set_current_state(TASK_INTERRUPTIBLE); |
|
// RX |
while ((skb = skb_dequeue(&sk->receive_queue))) { |
skb_orphan(skb); |
bnep_rx_frame(s, skb); |
} |
|
if (sk->state != BT_CONNECTED) |
break; |
|
// TX |
while ((skb = skb_dequeue(&sk->write_queue))) |
if (bnep_tx_frame(s, skb)) |
break; |
netif_wake_queue(dev); |
|
schedule(); |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(sk->sleep, &wait); |
|
/* Cleanup session */ |
down_write(&bnep_session_sem); |
|
/* Delete network device */ |
unregister_netdev(dev); |
|
/* Release the socket */ |
fput(s->sock->file); |
|
__bnep_unlink_session(s); |
|
up_write(&bnep_session_sem); |
kfree(s); |
return 0; |
} |
|
int bnep_add_connection(struct bnep_connadd_req *req, struct socket *sock) |
{ |
struct net_device *dev; |
struct bnep_session *s, *ss; |
u8 dst[ETH_ALEN], src[ETH_ALEN]; |
int err; |
|
BT_DBG(""); |
|
baswap((void *) dst, &bluez_pi(sock->sk)->dst); |
baswap((void *) src, &bluez_pi(sock->sk)->src); |
|
s = kmalloc(sizeof(struct bnep_session), GFP_KERNEL); |
if (!s) |
return -ENOMEM; |
memset(s, 0, sizeof(struct bnep_session)); |
|
down_write(&bnep_session_sem); |
|
ss = __bnep_get_session(dst); |
if (ss && ss->state == BT_CONNECTED) { |
err = -EEXIST; |
goto failed; |
} |
|
dev = &s->dev; |
|
if (*req->device) |
strcpy(dev->name, req->device); |
else |
strcpy(dev->name, "bnep%d"); |
|
memset(dev->broadcast, 0xff, ETH_ALEN); |
|
/* This is rx header therefor addresses are swaped. |
* ie eh.h_dest is our local address. */ |
memcpy(s->eh.h_dest, &src, ETH_ALEN); |
memcpy(s->eh.h_source, &dst, ETH_ALEN); |
|
s->sock = sock; |
s->role = req->role; |
s->state = BT_CONNECTED; |
|
s->msg.msg_flags = MSG_NOSIGNAL; |
|
#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER |
/* Set default mc filter */ |
set_bit(bnep_mc_hash(dev->broadcast), &s->mc_filter); |
#endif |
|
#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER |
/* Set default protocol filter */ |
|
/* (IPv4, ARP) */ |
s->proto_filter[0].start = htons(0x0800); |
s->proto_filter[0].end = htons(0x0806); |
/* (RARP, AppleTalk) */ |
s->proto_filter[1].start = htons(0x8035); |
s->proto_filter[1].end = htons(0x80F3); |
/* (IPX, IPv6) */ |
s->proto_filter[2].start = htons(0x8137); |
s->proto_filter[2].end = htons(0x86DD); |
#endif |
|
dev->init = bnep_net_init; |
dev->priv = s; |
err = register_netdev(dev); |
if (err) { |
goto failed; |
} |
|
__bnep_link_session(s); |
|
err = kernel_thread(bnep_session, s, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); |
if (err < 0) { |
/* Session thread start failed, gotta cleanup. */ |
unregister_netdev(dev); |
__bnep_unlink_session(s); |
goto failed; |
} |
|
up_write(&bnep_session_sem); |
strcpy(req->device, dev->name); |
return 0; |
|
failed: |
up_write(&bnep_session_sem); |
kfree(s); |
return err; |
} |
|
int bnep_del_connection(struct bnep_conndel_req *req) |
{ |
struct bnep_session *s; |
int err = 0; |
|
BT_DBG(""); |
|
down_read(&bnep_session_sem); |
|
s = __bnep_get_session(req->dst); |
if (s) { |
/* Wakeup user-space which is polling for socket errors. |
* This is temporary hack untill we have shutdown in L2CAP */ |
s->sock->sk->err = EUNATCH; |
|
/* Kill session thread */ |
atomic_inc(&s->killed); |
wake_up_interruptible(s->sock->sk->sleep); |
} else |
err = -ENOENT; |
|
up_read(&bnep_session_sem); |
return err; |
} |
|
static void __bnep_copy_ci(struct bnep_conninfo *ci, struct bnep_session *s) |
{ |
memcpy(ci->dst, s->eh.h_source, ETH_ALEN); |
strcpy(ci->device, s->dev.name); |
ci->flags = s->flags; |
ci->state = s->state; |
ci->role = s->role; |
} |
|
int bnep_get_connlist(struct bnep_connlist_req *req) |
{ |
struct list_head *p; |
int err = 0, n = 0; |
|
down_read(&bnep_session_sem); |
|
list_for_each(p, &bnep_session_list) { |
struct bnep_session *s; |
struct bnep_conninfo ci; |
|
s = list_entry(p, struct bnep_session, list); |
|
__bnep_copy_ci(&ci, s); |
|
if (copy_to_user(req->ci, &ci, sizeof(ci))) { |
err = -EFAULT; |
break; |
} |
|
if (++n >= req->cnum) |
break; |
|
req->ci++; |
} |
req->cnum = n; |
|
up_read(&bnep_session_sem); |
return err; |
} |
|
int bnep_get_conninfo(struct bnep_conninfo *ci) |
{ |
struct bnep_session *s; |
int err = 0; |
|
down_read(&bnep_session_sem); |
|
s = __bnep_get_session(ci->dst); |
if (s) |
__bnep_copy_ci(ci, s); |
else |
err = -ENOENT; |
|
up_read(&bnep_session_sem); |
return err; |
} |
|
static int __init bnep_init_module(void) |
{ |
l2cap_load(); |
|
bnep_sock_init(); |
|
BT_INFO("BlueZ BNEP ver %s", VERSION); |
BT_INFO("Copyright (C) 2001,2002 Inventel Systemes"); |
BT_INFO("Written 2001,2002 by Clement Moreau <clement.moreau@inventel.fr>"); |
BT_INFO("Written 2001,2002 by David Libault <david.libault@inventel.fr>"); |
BT_INFO("Copyright (C) 2002 Maxim Krasnyanskiy <maxk@qualcomm.com>"); |
|
return 0; |
} |
|
static void __exit bnep_cleanup_module(void) |
{ |
bnep_sock_cleanup(); |
} |
|
module_init(bnep_init_module); |
module_exit(bnep_cleanup_module); |
|
MODULE_DESCRIPTION("BlueZ BNEP ver " VERSION); |
MODULE_AUTHOR("David Libault <david.libault@inventel.fr>, Maxim Krasnyanskiy <maxk@qualcomm.com>"); |
MODULE_LICENSE("GPL"); |
/bnep/bnep.h
0,0 → 1,185
/* |
BNEP protocol definition for Linux Bluetooth stack (BlueZ). |
Copyright (C) 2002 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. |
|
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
|
You should have received a copy of the GNU General Public License |
along with this program; if not, write to the Free Software |
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
*/ |
|
/* |
* $Id: bnep.h,v 1.1.1.1 2004-04-15 01:17:07 phoenix Exp $ |
*/ |
|
#ifndef _BNEP_H |
#define _BNEP_H |
|
#include <linux/types.h> |
#include <net/bluetooth/bluetooth.h> |
|
#include <linux/crc32.h> |
|
// Limits |
#define BNEP_MAX_PROTO_FILTERS 5 |
#define BNEP_MAX_MULTICAST_FILTERS 20 |
|
// UUIDs |
#define BNEP_BASE_UUID 0x0000000000001000800000805F9B34FB |
#define BNEP_UUID16 0x02 |
#define BNEP_UUID32 0x04 |
#define BNEP_UUID128 0x16 |
|
#define BNEP_SVC_PANU 0x1115 |
#define BNEP_SVC_NAP 0x1116 |
#define BNEP_SVC_GN 0x1117 |
|
// Packet types |
#define BNEP_GENERAL 0x00 |
#define BNEP_CONTROL 0x01 |
#define BNEP_COMPRESSED 0x02 |
#define BNEP_COMPRESSED_SRC_ONLY 0x03 |
#define BNEP_COMPRESSED_DST_ONLY 0x04 |
|
// Control types |
#define BNEP_CMD_NOT_UNDERSTOOD 0x00 |
#define BNEP_SETUP_CONN_REQ 0x01 |
#define BNEP_SETUP_CONN_RSP 0x02 |
#define BNEP_FILTER_NET_TYPE_SET 0x03 |
#define BNEP_FILTER_NET_TYPE_RSP 0x04 |
#define BNEP_FILTER_MULTI_ADDR_SET 0x05 |
#define BNEP_FILTER_MULTI_ADDR_RSP 0x06 |
|
// Extension types |
#define BNEP_EXT_CONTROL 0x00 |
|
// Response messages |
#define BNEP_SUCCESS 0x00 |
|
#define BNEP_CONN_INVALID_DST 0x01 |
#define BNEP_CONN_INVALID_SRC 0x02 |
#define BNEP_CONN_INVALID_SVC 0x03 |
#define BNEP_CONN_NOT_ALLOWED 0x04 |
|
#define BNEP_FILTER_UNSUPPORTED_REQ 0x01 |
#define BNEP_FILTER_INVALID_RANGE 0x02 |
#define BNEP_FILTER_INVALID_MCADDR 0x02 |
#define BNEP_FILTER_LIMIT_REACHED 0x03 |
#define BNEP_FILTER_DENIED_SECURITY 0x04 |
|
// L2CAP settings |
#define BNEP_MTU 1691 |
#define BNEP_PSM 0x0f |
#define BNEP_FLUSH_TO 0xffff |
#define BNEP_CONNECT_TO 15 |
#define BNEP_FILTER_TO 15 |
|
// Headers |
#define BNEP_TYPE_MASK 0x7f |
#define BNEP_EXT_HEADER 0x80 |
|
struct bnep_setup_conn_req { |
__u8 type; |
__u8 ctrl; |
__u8 uuid_size; |
__u8 service[0]; |
} __attribute__((packed)); |
|
struct bnep_set_filter_req { |
__u8 type; |
__u8 ctrl; |
__u16 len; |
__u8 list[0]; |
} __attribute__((packed)); |
|
struct bnep_control_rsp { |
__u8 type; |
__u8 ctrl; |
__u16 resp; |
} __attribute__((packed)); |
|
struct bnep_ext_hdr { |
__u8 type; |
__u8 len; |
__u8 data[0]; |
} __attribute__((packed)); |
|
/* BNEP ioctl defines */ |
#define BNEPCONNADD _IOW('B', 200, int) |
#define BNEPCONNDEL _IOW('B', 201, int) |
#define BNEPGETCONNLIST _IOR('B', 210, int) |
#define BNEPGETCONNINFO _IOR('B', 211, int) |
|
struct bnep_connadd_req { |
int sock; // Connected socket |
__u32 flags; |
__u16 role; |
char device[16]; // Name of the Ethernet device |
}; |
|
struct bnep_conndel_req { |
__u32 flags; |
__u8 dst[ETH_ALEN]; |
}; |
|
struct bnep_conninfo { |
__u32 flags; |
__u16 role; |
__u16 state; |
__u8 dst[ETH_ALEN]; |
char device[16]; |
}; |
|
struct bnep_connlist_req { |
__u32 cnum; |
struct bnep_conninfo *ci; |
}; |
|
struct bnep_proto_filter { |
__u16 start; |
__u16 end; |
}; |
|
int bnep_add_connection(struct bnep_connadd_req *req, struct socket *sock); |
int bnep_del_connection(struct bnep_conndel_req *req); |
int bnep_get_connlist(struct bnep_connlist_req *req); |
int bnep_get_conninfo(struct bnep_conninfo *ci); |
|
// BNEP sessions |
struct bnep_session { |
struct list_head list; |
|
unsigned int role; |
unsigned long state; |
unsigned long flags; |
atomic_t killed; |
|
struct ethhdr eh; |
struct msghdr msg; |
|
struct bnep_proto_filter proto_filter[BNEP_MAX_PROTO_FILTERS]; |
u64 mc_filter; |
|
struct socket *sock; |
struct net_device dev; |
struct net_device_stats stats; |
}; |
|
int bnep_net_init(struct net_device *dev); |
int bnep_sock_init(void); |
int bnep_sock_cleanup(void); |
|
static inline int bnep_mc_hash(__u8 *addr) |
{ |
return (crc32_be(~0, addr, ETH_ALEN) >> 26); |
} |
|
#endif |
/bnep/Config.in
0,0 → 1,11
# |
# Bluetooth BNEP layer configuration |
# |
|
dep_tristate 'BNEP protocol support' CONFIG_BLUEZ_BNEP $CONFIG_BLUEZ_L2CAP |
|
if [ "$CONFIG_BLUEZ_BNEP" != "n" ]; then |
bool ' Multicast filter support' CONFIG_BLUEZ_BNEP_MC_FILTER |
bool ' Protocol filter support' CONFIG_BLUEZ_BNEP_PROTO_FILTER |
fi |
|
/bnep/Makefile
0,0 → 1,10
# |
# Makefile for the Linux Bluetooth BNEP layer |
# |
|
O_TARGET := bnep.o |
|
obj-y := core.o sock.o netdev.o |
obj-m += $(O_TARGET) |
|
include $(TOPDIR)/Rules.make |
/sco.c
0,0 → 1,1019
/* |
BlueZ - Bluetooth protocol stack for Linux |
Copyright (C) 2000-2001 Qualcomm Incorporated |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* BlueZ SCO sockets. |
* |
* $Id: sco.c,v 1.1.1.1 2004-04-15 01:17:01 phoenix Exp $ |
*/ |
#define VERSION "0.3" |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/init.h> |
#include <linux/skbuff.h> |
#include <linux/interrupt.h> |
#include <linux/socket.h> |
#include <linux/skbuff.h> |
#include <linux/proc_fs.h> |
#include <linux/list.h> |
#include <net/sock.h> |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/hci_core.h> |
#include <net/bluetooth/sco.h> |
|
#ifndef SCO_DEBUG |
#undef BT_DBG |
#define BT_DBG( A... ) |
#endif |
|
static struct proto_ops sco_sock_ops; |
|
static struct bluez_sock_list sco_sk_list = { |
lock: RW_LOCK_UNLOCKED |
}; |
|
static inline int sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent); |
static void sco_chan_del(struct sock *sk, int err); |
static inline struct sock * sco_chan_get(struct sco_conn *conn); |
|
static int sco_conn_del(struct hci_conn *conn, int err); |
|
static void sco_sock_close(struct sock *sk); |
static void sco_sock_kill(struct sock *sk); |
|
/* ----- SCO timers ------ */ |
static void sco_sock_timeout(unsigned long arg) |
{ |
struct sock *sk = (struct sock *) arg; |
|
BT_DBG("sock %p state %d", sk, sk->state); |
|
bh_lock_sock(sk); |
sk->err = ETIMEDOUT; |
sk->state_change(sk); |
bh_unlock_sock(sk); |
|
sco_sock_kill(sk); |
sock_put(sk); |
} |
|
static void sco_sock_set_timer(struct sock *sk, long timeout) |
{ |
BT_DBG("sock %p state %d timeout %ld", sk, sk->state, timeout); |
|
if (!mod_timer(&sk->timer, jiffies + timeout)) |
sock_hold(sk); |
} |
|
static void sco_sock_clear_timer(struct sock *sk) |
{ |
BT_DBG("sock %p state %d", sk, sk->state); |
|
if (timer_pending(&sk->timer) && del_timer(&sk->timer)) |
__sock_put(sk); |
} |
|
static void sco_sock_init_timer(struct sock *sk) |
{ |
init_timer(&sk->timer); |
sk->timer.function = sco_sock_timeout; |
sk->timer.data = (unsigned long)sk; |
} |
|
/* -------- SCO connections --------- */ |
static struct sco_conn *sco_conn_add(struct hci_conn *hcon, __u8 status) |
{ |
struct hci_dev *hdev = hcon->hdev; |
struct sco_conn *conn; |
|
if ((conn = hcon->sco_data)) |
return conn; |
|
if (status) |
return conn; |
|
if (!(conn = kmalloc(sizeof(struct sco_conn), GFP_ATOMIC))) |
return NULL; |
memset(conn, 0, sizeof(struct sco_conn)); |
|
spin_lock_init(&conn->lock); |
|
hcon->sco_data = conn; |
conn->hcon = hcon; |
|
conn->src = &hdev->bdaddr; |
conn->dst = &hcon->dst; |
|
if (hdev->sco_mtu > 0) |
conn->mtu = hdev->sco_mtu; |
else |
conn->mtu = 60; |
|
BT_DBG("hcon %p conn %p", hcon, conn); |
|
MOD_INC_USE_COUNT; |
return conn; |
} |
|
static int sco_conn_del(struct hci_conn *hcon, int err) |
{ |
struct sco_conn *conn; |
struct sock *sk; |
|
if (!(conn = hcon->sco_data)) |
return 0; |
|
BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); |
|
/* Kill socket */ |
if ((sk = sco_chan_get(conn))) { |
bh_lock_sock(sk); |
sco_sock_clear_timer(sk); |
sco_chan_del(sk, err); |
bh_unlock_sock(sk); |
sco_sock_kill(sk); |
} |
|
hcon->sco_data = NULL; |
kfree(conn); |
|
MOD_DEC_USE_COUNT; |
return 0; |
} |
|
int sco_connect(struct sock *sk) |
{ |
bdaddr_t *src = &bluez_pi(sk)->src; |
bdaddr_t *dst = &bluez_pi(sk)->dst; |
struct sco_conn *conn; |
struct hci_conn *hcon; |
struct hci_dev *hdev; |
int err = 0; |
|
BT_DBG("%s -> %s", batostr(src), batostr(dst)); |
|
if (!(hdev = hci_get_route(dst, src))) |
return -EHOSTUNREACH; |
|
hci_dev_lock_bh(hdev); |
|
err = -ENOMEM; |
|
hcon = hci_connect(hdev, SCO_LINK, dst); |
if (!hcon) |
goto done; |
|
conn = sco_conn_add(hcon, 0); |
if (!conn) { |
hci_conn_put(hcon); |
goto done; |
} |
|
/* Update source addr of the socket */ |
bacpy(src, conn->src); |
|
err = sco_chan_add(conn, sk, NULL); |
if (err) |
goto done; |
|
if (hcon->state == BT_CONNECTED) { |
sco_sock_clear_timer(sk); |
sk->state = BT_CONNECTED; |
} else { |
sk->state = BT_CONNECT; |
sco_sock_set_timer(sk, sk->sndtimeo); |
} |
done: |
hci_dev_unlock_bh(hdev); |
hci_dev_put(hdev); |
return err; |
} |
|
static inline int sco_send_frame(struct sock *sk, struct msghdr *msg, int len) |
{ |
struct sco_conn *conn = sco_pi(sk)->conn; |
struct sk_buff *skb; |
int err, count; |
|
/* Check outgoing MTU */ |
if (len > conn->mtu) |
return -EINVAL; |
|
BT_DBG("sk %p len %d", sk, len); |
|
count = MIN(conn->mtu, len); |
if (!(skb = bluez_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err))) |
return err; |
|
if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) { |
err = -EFAULT; |
goto fail; |
} |
|
if ((err = hci_send_sco(conn->hcon, skb)) < 0) |
goto fail; |
|
return count; |
|
fail: |
kfree_skb(skb); |
return err; |
} |
|
static inline void sco_recv_frame(struct sco_conn *conn, struct sk_buff *skb) |
{ |
struct sock *sk = sco_chan_get(conn); |
|
if (!sk) |
goto drop; |
|
BT_DBG("sk %p len %d", sk, skb->len); |
|
if (sk->state != BT_CONNECTED) |
goto drop; |
|
if (!sock_queue_rcv_skb(sk, skb)) |
return; |
|
drop: |
kfree_skb(skb); |
return; |
} |
|
/* -------- Socket interface ---------- */ |
static struct sock *__sco_get_sock_by_addr(bdaddr_t *ba) |
{ |
struct sock *sk; |
|
for (sk = sco_sk_list.head; sk; sk = sk->next) { |
if (!bacmp(&bluez_pi(sk)->src, ba)) |
break; |
} |
|
return sk; |
} |
|
/* Find socket listening on source bdaddr. |
* Returns closest match. |
*/ |
static struct sock *sco_get_sock_listen(bdaddr_t *src) |
{ |
struct sock *sk, *sk1 = NULL; |
|
read_lock(&sco_sk_list.lock); |
|
for (sk = sco_sk_list.head; sk; sk = sk->next) { |
if (sk->state != BT_LISTEN) |
continue; |
|
/* Exact match. */ |
if (!bacmp(&bluez_pi(sk)->src, src)) |
break; |
|
/* Closest match */ |
if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY)) |
sk1 = sk; |
} |
|
read_unlock(&sco_sk_list.lock); |
|
return sk ? sk : sk1; |
} |
|
static void sco_sock_destruct(struct sock *sk) |
{ |
BT_DBG("sk %p", sk); |
|
skb_queue_purge(&sk->receive_queue); |
skb_queue_purge(&sk->write_queue); |
|
MOD_DEC_USE_COUNT; |
} |
|
static void sco_sock_cleanup_listen(struct sock *parent) |
{ |
struct sock *sk; |
|
BT_DBG("parent %p", parent); |
|
/* Close not yet accepted channels */ |
while ((sk = bluez_accept_dequeue(parent, NULL))) { |
sco_sock_close(sk); |
sco_sock_kill(sk); |
} |
|
parent->state = BT_CLOSED; |
parent->zapped = 1; |
} |
|
/* Kill socket (only if zapped and orphan) |
* Must be called on unlocked socket. |
*/ |
static void sco_sock_kill(struct sock *sk) |
{ |
if (!sk->zapped || sk->socket) |
return; |
|
BT_DBG("sk %p state %d", sk, sk->state); |
|
/* Kill poor orphan */ |
bluez_sock_unlink(&sco_sk_list, sk); |
sk->dead = 1; |
sock_put(sk); |
} |
|
/* Close socket. |
* Must be called on unlocked socket. |
*/ |
static void sco_sock_close(struct sock *sk) |
{ |
struct sco_conn *conn; |
|
sco_sock_clear_timer(sk); |
|
lock_sock(sk); |
|
conn = sco_pi(sk)->conn; |
|
BT_DBG("sk %p state %d conn %p socket %p", sk, sk->state, conn, sk->socket); |
|
switch (sk->state) { |
case BT_LISTEN: |
sco_sock_cleanup_listen(sk); |
break; |
|
case BT_CONNECTED: |
case BT_CONFIG: |
case BT_CONNECT: |
case BT_DISCONN: |
sco_chan_del(sk, ECONNRESET); |
break; |
|
default: |
sk->zapped = 1; |
break; |
}; |
|
release_sock(sk); |
} |
|
static void sco_sock_init(struct sock *sk, struct sock *parent) |
{ |
BT_DBG("sk %p", sk); |
|
if (parent) |
sk->type = parent->type; |
} |
|
static struct sock *sco_sock_alloc(struct socket *sock, int proto, int prio) |
{ |
struct sock *sk; |
|
if (!(sk = sk_alloc(PF_BLUETOOTH, prio, 1))) |
return NULL; |
|
bluez_sock_init(sock, sk); |
|
sk->zapped = 0; |
|
sk->destruct = sco_sock_destruct; |
sk->sndtimeo = SCO_CONN_TIMEOUT; |
|
sk->protocol = proto; |
sk->state = BT_OPEN; |
|
sco_sock_init_timer(sk); |
|
bluez_sock_link(&sco_sk_list, sk); |
|
MOD_INC_USE_COUNT; |
return sk; |
} |
|
static int sco_sock_create(struct socket *sock, int protocol) |
{ |
struct sock *sk; |
|
BT_DBG("sock %p", sock); |
|
sock->state = SS_UNCONNECTED; |
|
if (sock->type != SOCK_SEQPACKET) |
return -ESOCKTNOSUPPORT; |
|
sock->ops = &sco_sock_ops; |
|
if (!(sk = sco_sock_alloc(sock, protocol, GFP_KERNEL))) |
return -ENOMEM; |
|
sco_sock_init(sk, NULL); |
return 0; |
} |
|
static int sco_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) |
{ |
struct sockaddr_sco *sa = (struct sockaddr_sco *) addr; |
struct sock *sk = sock->sk; |
bdaddr_t *src = &sa->sco_bdaddr; |
int err = 0; |
|
BT_DBG("sk %p %s", sk, batostr(&sa->sco_bdaddr)); |
|
if (!addr || addr->sa_family != AF_BLUETOOTH) |
return -EINVAL; |
|
lock_sock(sk); |
|
if (sk->state != BT_OPEN) { |
err = -EBADFD; |
goto done; |
} |
|
write_lock_bh(&sco_sk_list.lock); |
|
if (bacmp(src, BDADDR_ANY) && __sco_get_sock_by_addr(src)) { |
err = -EADDRINUSE; |
} else { |
/* Save source address */ |
bacpy(&bluez_pi(sk)->src, &sa->sco_bdaddr); |
sk->state = BT_BOUND; |
} |
|
write_unlock_bh(&sco_sk_list.lock); |
|
done: |
release_sock(sk); |
|
return err; |
} |
|
static int sco_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags) |
{ |
struct sockaddr_sco *sa = (struct sockaddr_sco *) addr; |
struct sock *sk = sock->sk; |
int err = 0; |
|
|
BT_DBG("sk %p", sk); |
|
if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_sco)) |
return -EINVAL; |
|
if (sk->state != BT_OPEN && sk->state != BT_BOUND) |
return -EBADFD; |
|
if (sk->type != SOCK_SEQPACKET) |
return -EINVAL; |
|
lock_sock(sk); |
|
/* Set destination address and psm */ |
bacpy(&bluez_pi(sk)->dst, &sa->sco_bdaddr); |
|
if ((err = sco_connect(sk))) |
goto done; |
|
err = bluez_sock_wait_state(sk, BT_CONNECTED, |
sock_sndtimeo(sk, flags & O_NONBLOCK)); |
|
done: |
release_sock(sk); |
return err; |
} |
|
int sco_sock_listen(struct socket *sock, int backlog) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sk %p backlog %d", sk, backlog); |
|
lock_sock(sk); |
|
if (sk->state != BT_BOUND || sock->type != SOCK_SEQPACKET) { |
err = -EBADFD; |
goto done; |
} |
|
sk->max_ack_backlog = backlog; |
sk->ack_backlog = 0; |
sk->state = BT_LISTEN; |
|
done: |
release_sock(sk); |
return err; |
} |
|
int sco_sock_accept(struct socket *sock, struct socket *newsock, int flags) |
{ |
DECLARE_WAITQUEUE(wait, current); |
struct sock *sk = sock->sk, *ch; |
long timeo; |
int err = 0; |
|
lock_sock(sk); |
|
if (sk->state != BT_LISTEN) { |
err = -EBADFD; |
goto done; |
} |
|
timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); |
|
BT_DBG("sk %p timeo %ld", sk, timeo); |
|
/* Wait for an incoming connection. (wake-one). */ |
add_wait_queue_exclusive(sk->sleep, &wait); |
while (!(ch = bluez_accept_dequeue(sk, newsock))) { |
set_current_state(TASK_INTERRUPTIBLE); |
if (!timeo) { |
err = -EAGAIN; |
break; |
} |
|
release_sock(sk); |
timeo = schedule_timeout(timeo); |
lock_sock(sk); |
|
if (sk->state != BT_LISTEN) { |
err = -EBADFD; |
break; |
} |
|
if (signal_pending(current)) { |
err = sock_intr_errno(timeo); |
break; |
} |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(sk->sleep, &wait); |
|
if (err) |
goto done; |
|
newsock->state = SS_CONNECTED; |
|
BT_DBG("new socket %p", ch); |
|
done: |
release_sock(sk); |
return err; |
} |
|
static int sco_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer) |
{ |
struct sockaddr_sco *sa = (struct sockaddr_sco *) addr; |
struct sock *sk = sock->sk; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
addr->sa_family = AF_BLUETOOTH; |
*len = sizeof(struct sockaddr_sco); |
|
if (peer) |
bacpy(&sa->sco_bdaddr, &bluez_pi(sk)->dst); |
else |
bacpy(&sa->sco_bdaddr, &bluez_pi(sk)->src); |
|
return 0; |
} |
|
static int sco_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
if (sk->err) |
return sock_error(sk); |
|
if (msg->msg_flags & MSG_OOB) |
return -EOPNOTSUPP; |
|
lock_sock(sk); |
|
if (sk->state == BT_CONNECTED) |
err = sco_send_frame(sk, msg, len); |
else |
err = -ENOTCONN; |
|
release_sock(sk); |
return err; |
} |
|
int sco_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sk %p", sk); |
|
lock_sock(sk); |
|
switch (optname) { |
default: |
err = -ENOPROTOOPT; |
break; |
}; |
|
release_sock(sk); |
return err; |
} |
|
int sco_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen) |
{ |
struct sock *sk = sock->sk; |
struct sco_options opts; |
struct sco_conninfo cinfo; |
int len, err = 0; |
|
BT_DBG("sk %p", sk); |
|
if (get_user(len, optlen)) |
return -EFAULT; |
|
lock_sock(sk); |
|
switch (optname) { |
case SCO_OPTIONS: |
if (sk->state != BT_CONNECTED) { |
err = -ENOTCONN; |
break; |
} |
|
opts.mtu = sco_pi(sk)->conn->mtu; |
|
BT_DBG("mtu %d", opts.mtu); |
|
len = MIN(len, sizeof(opts)); |
if (copy_to_user(optval, (char *)&opts, len)) |
err = -EFAULT; |
|
break; |
|
case SCO_CONNINFO: |
if (sk->state != BT_CONNECTED) { |
err = -ENOTCONN; |
break; |
} |
|
cinfo.hci_handle = sco_pi(sk)->conn->hcon->handle; |
|
len = MIN(len, sizeof(cinfo)); |
if (copy_to_user(optval, (char *)&cinfo, len)) |
err = -EFAULT; |
|
break; |
|
default: |
err = -ENOPROTOOPT; |
break; |
}; |
|
release_sock(sk); |
return err; |
} |
|
static int sco_sock_release(struct socket *sock) |
{ |
struct sock *sk = sock->sk; |
int err = 0; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
if (!sk) |
return 0; |
|
sco_sock_close(sk); |
if (sk->linger) { |
lock_sock(sk); |
err = bluez_sock_wait_state(sk, BT_CLOSED, sk->lingertime); |
release_sock(sk); |
} |
|
sock_orphan(sk); |
sco_sock_kill(sk); |
return err; |
} |
|
static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent) |
{ |
BT_DBG("conn %p", conn); |
|
sco_pi(sk)->conn = conn; |
conn->sk = sk; |
|
if (parent) |
bluez_accept_enqueue(parent, sk); |
} |
|
static inline int sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent) |
{ |
int err = 0; |
|
sco_conn_lock(conn); |
if (conn->sk) { |
err = -EBUSY; |
} else { |
__sco_chan_add(conn, sk, parent); |
} |
sco_conn_unlock(conn); |
return err; |
} |
|
static inline struct sock * sco_chan_get(struct sco_conn *conn) |
{ |
struct sock *sk = NULL; |
sco_conn_lock(conn); |
sk = conn->sk; |
sco_conn_unlock(conn); |
return sk; |
} |
|
/* Delete channel. |
* Must be called on the locked socket. */ |
static void sco_chan_del(struct sock *sk, int err) |
{ |
struct sco_conn *conn; |
|
conn = sco_pi(sk)->conn; |
|
BT_DBG("sk %p, conn %p, err %d", sk, conn, err); |
|
if (conn) { |
sco_conn_lock(conn); |
conn->sk = NULL; |
sco_pi(sk)->conn = NULL; |
sco_conn_unlock(conn); |
hci_conn_put(conn->hcon); |
} |
|
sk->state = BT_CLOSED; |
sk->err = err; |
sk->state_change(sk); |
|
sk->zapped = 1; |
} |
|
static void sco_conn_ready(struct sco_conn *conn) |
{ |
struct sock *parent, *sk; |
|
BT_DBG("conn %p", conn); |
|
sco_conn_lock(conn); |
|
if ((sk = conn->sk)) { |
sco_sock_clear_timer(sk); |
bh_lock_sock(sk); |
sk->state = BT_CONNECTED; |
sk->state_change(sk); |
bh_unlock_sock(sk); |
} else { |
parent = sco_get_sock_listen(conn->src); |
if (!parent) |
goto done; |
|
bh_lock_sock(parent); |
|
sk = sco_sock_alloc(NULL, BTPROTO_SCO, GFP_ATOMIC); |
if (!sk) { |
bh_unlock_sock(parent); |
goto done; |
} |
|
sco_sock_init(sk, parent); |
|
bacpy(&bluez_pi(sk)->src, conn->src); |
bacpy(&bluez_pi(sk)->dst, conn->dst); |
|
hci_conn_hold(conn->hcon); |
__sco_chan_add(conn, sk, parent); |
|
sk->state = BT_CONNECTED; |
|
/* Wake up parent */ |
parent->data_ready(parent, 1); |
|
bh_unlock_sock(parent); |
} |
|
done: |
sco_conn_unlock(conn); |
} |
|
/* ----- SCO interface with lower layer (HCI) ----- */ |
int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type) |
{ |
BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr)); |
|
/* Always accept connection */ |
return HCI_LM_ACCEPT; |
} |
|
int sco_connect_cfm(struct hci_conn *hcon, __u8 status) |
{ |
BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status); |
|
if (hcon->type != SCO_LINK) |
return 0; |
|
if (!status) { |
struct sco_conn *conn; |
|
conn = sco_conn_add(hcon, status); |
if (conn) |
sco_conn_ready(conn); |
} else |
sco_conn_del(hcon, bterr(status)); |
|
return 0; |
} |
|
int sco_disconn_ind(struct hci_conn *hcon, __u8 reason) |
{ |
BT_DBG("hcon %p reason %d", hcon, reason); |
|
if (hcon->type != SCO_LINK) |
return 0; |
|
sco_conn_del(hcon, bterr(reason)); |
return 0; |
} |
|
int sco_recv_scodata(struct hci_conn *hcon, struct sk_buff *skb) |
{ |
struct sco_conn *conn = hcon->sco_data; |
|
if (!conn) |
goto drop; |
|
BT_DBG("conn %p len %d", conn, skb->len); |
|
if (skb->len) { |
sco_recv_frame(conn, skb); |
return 0; |
} |
|
drop: |
kfree_skb(skb); |
return 0; |
} |
|
/* ----- Proc fs support ------ */ |
static int sco_sock_dump(char *buf, struct bluez_sock_list *list) |
{ |
struct sco_pinfo *pi; |
struct sock *sk; |
char *ptr = buf; |
|
write_lock_bh(&list->lock); |
|
for (sk = list->head; sk; sk = sk->next) { |
pi = sco_pi(sk); |
ptr += sprintf(ptr, "%s %s %d\n", |
batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst), |
sk->state); |
} |
|
write_unlock_bh(&list->lock); |
|
ptr += sprintf(ptr, "\n"); |
|
return ptr - buf; |
} |
|
static int sco_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv) |
{ |
char *ptr = buf; |
int len; |
|
BT_DBG("count %d, offset %ld", count, offset); |
|
ptr += sco_sock_dump(ptr, &sco_sk_list); |
len = ptr - buf; |
|
if (len <= count + offset) |
*eof = 1; |
|
*start = buf + offset; |
len -= offset; |
|
if (len > count) |
len = count; |
if (len < 0) |
len = 0; |
|
return len; |
} |
|
static struct proto_ops sco_sock_ops = { |
family: PF_BLUETOOTH, |
release: sco_sock_release, |
bind: sco_sock_bind, |
connect: sco_sock_connect, |
listen: sco_sock_listen, |
accept: sco_sock_accept, |
getname: sco_sock_getname, |
sendmsg: sco_sock_sendmsg, |
recvmsg: bluez_sock_recvmsg, |
poll: bluez_sock_poll, |
socketpair: sock_no_socketpair, |
ioctl: sock_no_ioctl, |
shutdown: sock_no_shutdown, |
setsockopt: sco_sock_setsockopt, |
getsockopt: sco_sock_getsockopt, |
mmap: sock_no_mmap |
}; |
|
static struct net_proto_family sco_sock_family_ops = { |
family: PF_BLUETOOTH, |
create: sco_sock_create |
}; |
|
static struct hci_proto sco_hci_proto = { |
name: "SCO", |
id: HCI_PROTO_SCO, |
connect_ind: sco_connect_ind, |
connect_cfm: sco_connect_cfm, |
disconn_ind: sco_disconn_ind, |
recv_scodata: sco_recv_scodata, |
}; |
|
int __init sco_init(void) |
{ |
int err; |
|
if ((err = bluez_sock_register(BTPROTO_SCO, &sco_sock_family_ops))) { |
BT_ERR("Can't register SCO socket layer"); |
return err; |
} |
|
if ((err = hci_register_proto(&sco_hci_proto))) { |
BT_ERR("Can't register SCO protocol"); |
return err; |
} |
|
create_proc_read_entry("bluetooth/sco", 0, 0, sco_read_proc, NULL); |
|
BT_INFO("BlueZ SCO ver %s Copyright (C) 2000,2001 Qualcomm Inc", VERSION); |
BT_INFO("Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>"); |
return 0; |
} |
|
void sco_cleanup(void) |
{ |
int err; |
|
remove_proc_entry("bluetooth/sco", NULL); |
|
/* Unregister socket, protocol and notifier */ |
if ((err = bluez_sock_unregister(BTPROTO_SCO))) |
BT_ERR("Can't unregister SCO socket layer %d", err); |
|
if ((err = hci_unregister_proto(&sco_hci_proto))) |
BT_ERR("Can't unregister SCO protocol %d", err); |
} |
|
module_init(sco_init); |
module_exit(sco_cleanup); |
|
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>"); |
MODULE_DESCRIPTION("BlueZ SCO ver " VERSION); |
MODULE_LICENSE("GPL"); |
/hci_event.c
0,0 → 1,910
/* |
BlueZ - Bluetooth protocol stack for Linux |
Copyright (C) 2000-2001 Qualcomm Incorporated |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* HCI Events. |
* |
* $Id: hci_event.c,v 1.1.1.1 2004-04-15 01:17:06 phoenix Exp $ |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/init.h> |
#include <linux/skbuff.h> |
#include <linux/interrupt.h> |
#include <linux/notifier.h> |
#include <net/sock.h> |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
#include <asm/unaligned.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/hci_core.h> |
|
#ifndef HCI_CORE_DEBUG |
#undef BT_DBG |
#define BT_DBG( A... ) |
#endif |
|
/* Handle HCI Event packets */ |
|
/* Command Complete OGF LINK_CTL */ |
static void hci_cc_link_ctl(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) |
{ |
__u8 status; |
|
BT_DBG("%s ocf 0x%x", hdev->name, ocf); |
|
switch (ocf) { |
case OCF_INQUIRY_CANCEL: |
status = *((__u8 *) skb->data); |
|
if (status) { |
BT_DBG("%s Inquiry cancel error: status 0x%x", hdev->name, status); |
} else { |
clear_bit(HCI_INQUIRY, &hdev->flags); |
hci_req_complete(hdev, status); |
} |
break; |
|
default: |
BT_DBG("%s Command complete: ogf LINK_CTL ocf %x", hdev->name, ocf); |
break; |
}; |
} |
|
/* Command Complete OGF LINK_POLICY */ |
static void hci_cc_link_policy(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) |
{ |
struct hci_conn *conn; |
role_discovery_rp *rd; |
|
BT_DBG("%s ocf 0x%x", hdev->name, ocf); |
|
switch (ocf) { |
case OCF_ROLE_DISCOVERY: |
rd = (void *) skb->data; |
|
if (rd->status) |
break; |
|
hci_dev_lock(hdev); |
|
conn = conn_hash_lookup_handle(hdev, __le16_to_cpu(rd->handle)); |
if (conn) { |
if (rd->role) |
conn->link_mode &= ~HCI_LM_MASTER; |
else |
conn->link_mode |= HCI_LM_MASTER; |
} |
|
hci_dev_unlock(hdev); |
break; |
|
default: |
BT_DBG("%s: Command complete: ogf LINK_POLICY ocf %x", |
hdev->name, ocf); |
break; |
}; |
} |
|
/* Command Complete OGF HOST_CTL */ |
static void hci_cc_host_ctl(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) |
{ |
__u8 status, param; |
void *sent; |
|
BT_DBG("%s ocf 0x%x", hdev->name, ocf); |
|
switch (ocf) { |
case OCF_RESET: |
status = *((__u8 *) skb->data); |
hci_req_complete(hdev, status); |
break; |
|
case OCF_SET_EVENT_FLT: |
status = *((__u8 *) skb->data); |
if (status) { |
BT_DBG("%s SET_EVENT_FLT failed %d", hdev->name, status); |
} else { |
BT_DBG("%s SET_EVENT_FLT succeseful", hdev->name); |
} |
break; |
|
case OCF_WRITE_AUTH_ENABLE: |
sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_AUTH_ENABLE); |
if (!sent) |
break; |
|
status = *((__u8 *) skb->data); |
param = *((__u8 *) sent); |
|
if (!status) { |
if (param == AUTH_ENABLED) |
set_bit(HCI_AUTH, &hdev->flags); |
else |
clear_bit(HCI_AUTH, &hdev->flags); |
} |
hci_req_complete(hdev, status); |
break; |
|
case OCF_WRITE_ENCRYPT_MODE: |
sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_ENCRYPT_MODE); |
if (!sent) |
break; |
|
status = *((__u8 *) skb->data); |
param = *((__u8 *) sent); |
|
if (!status) { |
if (param) |
set_bit(HCI_ENCRYPT, &hdev->flags); |
else |
clear_bit(HCI_ENCRYPT, &hdev->flags); |
} |
hci_req_complete(hdev, status); |
break; |
|
case OCF_WRITE_CA_TIMEOUT: |
status = *((__u8 *) skb->data); |
if (status) { |
BT_DBG("%s OCF_WRITE_CA_TIMEOUT failed %d", hdev->name, status); |
} else { |
BT_DBG("%s OCF_WRITE_CA_TIMEOUT succeseful", hdev->name); |
} |
break; |
|
case OCF_WRITE_PG_TIMEOUT: |
status = *((__u8 *) skb->data); |
if (status) { |
BT_DBG("%s OCF_WRITE_PG_TIMEOUT failed %d", hdev->name, status); |
} else { |
BT_DBG("%s: OCF_WRITE_PG_TIMEOUT succeseful", hdev->name); |
} |
break; |
|
case OCF_WRITE_SCAN_ENABLE: |
sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE); |
if (!sent) |
break; |
status = *((__u8 *) skb->data); |
param = *((__u8 *) sent); |
|
BT_DBG("param 0x%x", param); |
|
if (!status) { |
clear_bit(HCI_PSCAN, &hdev->flags); |
clear_bit(HCI_ISCAN, &hdev->flags); |
if (param & SCAN_INQUIRY) |
set_bit(HCI_ISCAN, &hdev->flags); |
|
if (param & SCAN_PAGE) |
set_bit(HCI_PSCAN, &hdev->flags); |
} |
hci_req_complete(hdev, status); |
break; |
|
case OCF_HOST_BUFFER_SIZE: |
status = *((__u8 *) skb->data); |
if (status) { |
BT_DBG("%s OCF_BUFFER_SIZE failed %d", hdev->name, status); |
hci_req_complete(hdev, status); |
} |
break; |
|
default: |
BT_DBG("%s Command complete: ogf HOST_CTL ocf %x", hdev->name, ocf); |
break; |
}; |
} |
|
/* Command Complete OGF INFO_PARAM */ |
static void hci_cc_info_param(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) |
{ |
read_local_features_rp *lf; |
read_buffer_size_rp *bs; |
read_bd_addr_rp *ba; |
|
BT_DBG("%s ocf 0x%x", hdev->name, ocf); |
|
switch (ocf) { |
case OCF_READ_LOCAL_FEATURES: |
lf = (read_local_features_rp *) skb->data; |
|
if (lf->status) { |
BT_DBG("%s READ_LOCAL_FEATURES failed %d", hdev->name, lf->status); |
break; |
} |
|
memcpy(hdev->features, lf->features, sizeof(hdev->features)); |
|
/* Adjust default settings according to features |
* supported by device. */ |
if (hdev->features[0] & LMP_3SLOT) |
hdev->pkt_type |= (HCI_DM3 | HCI_DH3); |
|
if (hdev->features[0] & LMP_5SLOT) |
hdev->pkt_type |= (HCI_DM5 | HCI_DH5); |
|
if (hdev->features[1] & LMP_HV2) |
hdev->pkt_type |= (HCI_HV2); |
|
if (hdev->features[1] & LMP_HV3) |
hdev->pkt_type |= (HCI_HV3); |
|
BT_DBG("%s: features 0x%x 0x%x 0x%x", hdev->name, lf->features[0], lf->features[1], lf->features[2]); |
|
break; |
|
case OCF_READ_BUFFER_SIZE: |
bs = (read_buffer_size_rp *) skb->data; |
|
if (bs->status) { |
BT_DBG("%s READ_BUFFER_SIZE failed %d", hdev->name, bs->status); |
hci_req_complete(hdev, bs->status); |
break; |
} |
|
hdev->acl_mtu = __le16_to_cpu(bs->acl_mtu); |
hdev->sco_mtu = bs->sco_mtu ? bs->sco_mtu : 64; |
hdev->acl_pkts = hdev->acl_cnt = __le16_to_cpu(bs->acl_max_pkt); |
hdev->sco_pkts = hdev->sco_cnt = __le16_to_cpu(bs->sco_max_pkt); |
|
BT_DBG("%s mtu: acl %d, sco %d max_pkt: acl %d, sco %d", hdev->name, |
hdev->acl_mtu, hdev->sco_mtu, hdev->acl_pkts, hdev->sco_pkts); |
break; |
|
case OCF_READ_BD_ADDR: |
ba = (read_bd_addr_rp *) skb->data; |
|
if (!ba->status) { |
bacpy(&hdev->bdaddr, &ba->bdaddr); |
} else { |
BT_DBG("%s: READ_BD_ADDR failed %d", hdev->name, ba->status); |
} |
|
hci_req_complete(hdev, ba->status); |
break; |
|
default: |
BT_DBG("%s Command complete: ogf INFO_PARAM ocf %x", hdev->name, ocf); |
break; |
}; |
} |
|
/* Command Status OGF LINK_CTL */ |
static inline void hci_cs_create_conn(struct hci_dev *hdev, __u8 status) |
{ |
struct hci_conn *conn; |
create_conn_cp *cc = hci_sent_cmd_data(hdev, OGF_LINK_CTL, OCF_CREATE_CONN); |
|
if (!cc) |
return; |
|
hci_dev_lock(hdev); |
|
conn = conn_hash_lookup_ba(hdev, ACL_LINK, &cc->bdaddr); |
|
BT_DBG("%s status 0x%x bdaddr %s conn %p", hdev->name, |
status, batostr(&cc->bdaddr), conn); |
|
if (status) { |
if (conn) { |
conn->state = BT_CLOSED; |
hci_proto_connect_cfm(conn, status); |
hci_conn_del(conn); |
} |
} else { |
if (!conn) { |
conn = hci_conn_add(hdev, ACL_LINK, &cc->bdaddr); |
if (conn) { |
conn->out = 1; |
conn->link_mode |= HCI_LM_MASTER; |
} else |
BT_ERR("No memmory for new connection"); |
} |
} |
|
hci_dev_unlock(hdev); |
} |
|
static void hci_cs_link_ctl(struct hci_dev *hdev, __u16 ocf, __u8 status) |
{ |
BT_DBG("%s ocf 0x%x", hdev->name, ocf); |
|
switch (ocf) { |
case OCF_CREATE_CONN: |
hci_cs_create_conn(hdev, status); |
break; |
|
case OCF_ADD_SCO: |
if (status) { |
struct hci_conn *acl, *sco; |
add_sco_cp *cp = hci_sent_cmd_data(hdev, |
OGF_LINK_CTL, OCF_ADD_SCO); |
__u16 handle; |
|
if (!cp) |
break; |
|
handle = __le16_to_cpu(cp->handle); |
|
BT_DBG("%s Add SCO error: handle %d status 0x%x", hdev->name, handle, status); |
|
hci_dev_lock(hdev); |
|
acl = conn_hash_lookup_handle(hdev, handle); |
if (acl && (sco = acl->link)) { |
sco->state = BT_CLOSED; |
hci_proto_connect_cfm(sco, status); |
hci_conn_del(sco); |
} |
|
hci_dev_unlock(hdev); |
} |
break; |
|
case OCF_INQUIRY: |
if (status) { |
BT_DBG("%s Inquiry error: status 0x%x", hdev->name, status); |
hci_req_complete(hdev, status); |
} else { |
set_bit(HCI_INQUIRY, &hdev->flags); |
} |
break; |
|
default: |
BT_DBG("%s Command status: ogf LINK_CTL ocf %x status %d", |
hdev->name, ocf, status); |
break; |
}; |
} |
|
/* Command Status OGF LINK_POLICY */ |
static void hci_cs_link_policy(struct hci_dev *hdev, __u16 ocf, __u8 status) |
{ |
BT_DBG("%s ocf 0x%x", hdev->name, ocf); |
|
switch (ocf) { |
default: |
BT_DBG("%s Command status: ogf HOST_POLICY ocf %x", hdev->name, ocf); |
break; |
}; |
} |
|
/* Command Status OGF HOST_CTL */ |
static void hci_cs_host_ctl(struct hci_dev *hdev, __u16 ocf, __u8 status) |
{ |
BT_DBG("%s ocf 0x%x", hdev->name, ocf); |
|
switch (ocf) { |
default: |
BT_DBG("%s Command status: ogf HOST_CTL ocf %x", hdev->name, ocf); |
break; |
}; |
} |
|
/* Command Status OGF INFO_PARAM */ |
static void hci_cs_info_param(struct hci_dev *hdev, __u16 ocf, __u8 status) |
{ |
BT_DBG("%s: hci_cs_info_param: ocf 0x%x", hdev->name, ocf); |
|
switch (ocf) { |
default: |
BT_DBG("%s Command status: ogf INFO_PARAM ocf %x", hdev->name, ocf); |
break; |
}; |
} |
|
/* Inquiry Complete */ |
static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
__u8 status = *((__u8 *) skb->data); |
|
BT_DBG("%s status %d", hdev->name, status); |
|
clear_bit(HCI_INQUIRY, &hdev->flags); |
hci_req_complete(hdev, status); |
} |
|
/* Inquiry Result */ |
static inline void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
inquiry_info *info = (inquiry_info *) (skb->data + 1); |
int num_rsp = *((__u8 *) skb->data); |
|
BT_DBG("%s num_rsp %d", hdev->name, num_rsp); |
|
hci_dev_lock(hdev); |
for (; num_rsp; num_rsp--) |
inquiry_cache_update(hdev, info++); |
hci_dev_unlock(hdev); |
} |
|
/* Inquiry Result With RSSI */ |
static inline void hci_inquiry_result_with_rssi_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
inquiry_info_with_rssi *info = (inquiry_info_with_rssi *) (skb->data + 1); |
int num_rsp = *((__u8 *) skb->data); |
|
BT_DBG("%s num_rsp %d", hdev->name, num_rsp); |
|
hci_dev_lock(hdev); |
for (; num_rsp; num_rsp--) { |
inquiry_info tmp; |
bacpy(&tmp.bdaddr, &info->bdaddr); |
tmp.pscan_rep_mode = info->pscan_rep_mode; |
tmp.pscan_period_mode = info->pscan_period_mode; |
tmp.pscan_mode = 0x00; |
memcpy(tmp.dev_class, &info->dev_class, 3); |
tmp.clock_offset = info->clock_offset; |
info++; |
inquiry_cache_update(hdev, &tmp); |
} |
hci_dev_unlock(hdev); |
} |
|
/* Connect Request */ |
static inline void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
evt_conn_request *cr = (evt_conn_request *) skb->data; |
int mask = hdev->link_mode; |
|
BT_DBG("%s Connection request: %s type 0x%x", hdev->name, |
batostr(&cr->bdaddr), cr->link_type); |
|
mask |= hci_proto_connect_ind(hdev, &cr->bdaddr, cr->link_type); |
|
if (mask & HCI_LM_ACCEPT) { |
/* Connection accepted */ |
struct hci_conn *conn; |
accept_conn_req_cp ac; |
|
hci_dev_lock(hdev); |
conn = conn_hash_lookup_ba(hdev, cr->link_type, &cr->bdaddr); |
if (!conn) { |
if (!(conn = hci_conn_add(hdev, cr->link_type, &cr->bdaddr))) { |
BT_ERR("No memmory for new connection"); |
hci_dev_unlock(hdev); |
return; |
} |
} |
conn->state = BT_CONNECT; |
hci_dev_unlock(hdev); |
|
bacpy(&ac.bdaddr, &cr->bdaddr); |
|
if (lmp_rswitch_capable(hdev) && (mask & HCI_LM_MASTER)) |
ac.role = 0x00; /* Become master */ |
else |
ac.role = 0x01; /* Remain slave */ |
|
hci_send_cmd(hdev, OGF_LINK_CTL, OCF_ACCEPT_CONN_REQ, |
ACCEPT_CONN_REQ_CP_SIZE, &ac); |
} else { |
/* Connection rejected */ |
reject_conn_req_cp rc; |
|
bacpy(&rc.bdaddr, &cr->bdaddr); |
rc.reason = 0x0f; |
hci_send_cmd(hdev, OGF_LINK_CTL, OCF_REJECT_CONN_REQ, |
REJECT_CONN_REQ_CP_SIZE, &rc); |
} |
} |
|
/* Connect Complete */ |
static inline void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
evt_conn_complete *cc = (evt_conn_complete *) skb->data; |
struct hci_conn *conn = NULL; |
|
BT_DBG("%s", hdev->name); |
|
hci_dev_lock(hdev); |
|
conn = conn_hash_lookup_ba(hdev, cc->link_type, &cc->bdaddr); |
if (!conn) { |
hci_dev_unlock(hdev); |
return; |
} |
|
if (!cc->status) { |
conn->handle = __le16_to_cpu(cc->handle); |
conn->state = BT_CONNECTED; |
|
if (test_bit(HCI_AUTH, &hdev->flags)) |
conn->link_mode |= HCI_LM_AUTH; |
|
if (test_bit(HCI_ENCRYPT, &hdev->flags)) |
conn->link_mode |= HCI_LM_ENCRYPT; |
|
|
/* Set link policy */ |
if (conn->type == ACL_LINK && hdev->link_policy) { |
write_link_policy_cp lp; |
lp.handle = cc->handle; |
lp.policy = __cpu_to_le16(hdev->link_policy); |
hci_send_cmd(hdev, OGF_LINK_POLICY, OCF_WRITE_LINK_POLICY, |
WRITE_LINK_POLICY_CP_SIZE, &lp); |
} |
|
/* Set packet type for incomming connection */ |
if (!conn->out) { |
change_conn_ptype_cp cp; |
cp.handle = cc->handle; |
cp.pkt_type = (conn->type == ACL_LINK) ? |
__cpu_to_le16(hdev->pkt_type & ACL_PTYPE_MASK): |
__cpu_to_le16(hdev->pkt_type & SCO_PTYPE_MASK); |
|
hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CHANGE_CONN_PTYPE, |
CHANGE_CONN_PTYPE_CP_SIZE, &cp); |
} |
} else |
conn->state = BT_CLOSED; |
|
if (conn->type == ACL_LINK) { |
struct hci_conn *sco = conn->link; |
if (sco) { |
if (!cc->status) |
hci_add_sco(sco, conn->handle); |
else { |
hci_proto_connect_cfm(sco, cc->status); |
hci_conn_del(sco); |
} |
} |
} |
|
hci_proto_connect_cfm(conn, cc->status); |
if (cc->status) |
hci_conn_del(conn); |
|
hci_dev_unlock(hdev); |
} |
|
/* Disconnect Complete */ |
static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
evt_disconn_complete *dc = (evt_disconn_complete *) skb->data; |
struct hci_conn *conn = NULL; |
__u16 handle = __le16_to_cpu(dc->handle); |
|
BT_DBG("%s status %d", hdev->name, dc->status); |
|
if (dc->status) |
return; |
|
hci_dev_lock(hdev); |
|
conn = conn_hash_lookup_handle(hdev, handle); |
if (conn) { |
conn->state = BT_CLOSED; |
hci_proto_disconn_ind(conn, dc->reason); |
hci_conn_del(conn); |
} |
|
hci_dev_unlock(hdev); |
} |
|
/* Number of completed packets */ |
static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
evt_num_comp_pkts *nc = (evt_num_comp_pkts *) skb->data; |
__u16 *ptr; |
int i; |
|
skb_pull(skb, EVT_NUM_COMP_PKTS_SIZE); |
|
BT_DBG("%s num_hndl %d", hdev->name, nc->num_hndl); |
|
if (skb->len < nc->num_hndl * 4) { |
BT_DBG("%s bad parameters", hdev->name); |
return; |
} |
|
tasklet_disable(&hdev->tx_task); |
|
for (i = 0, ptr = (__u16 *) skb->data; i < nc->num_hndl; i++) { |
struct hci_conn *conn; |
__u16 handle, count; |
|
handle = __le16_to_cpu(get_unaligned(ptr++)); |
count = __le16_to_cpu(get_unaligned(ptr++)); |
|
conn = conn_hash_lookup_handle(hdev, handle); |
if (conn) { |
conn->sent -= count; |
|
if (conn->type == SCO_LINK) { |
if ((hdev->sco_cnt += count) > hdev->sco_pkts) |
hdev->sco_cnt = hdev->sco_pkts; |
} else { |
if ((hdev->acl_cnt += count) > hdev->acl_pkts) |
hdev->acl_cnt = hdev->acl_pkts; |
} |
} |
} |
hci_sched_tx(hdev); |
|
tasklet_enable(&hdev->tx_task); |
} |
|
/* Role Change */ |
static inline void hci_role_change_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
evt_role_change *rc = (evt_role_change *) skb->data; |
struct hci_conn *conn = NULL; |
|
BT_DBG("%s status %d", hdev->name, rc->status); |
|
if (rc->status) |
return; |
|
hci_dev_lock(hdev); |
|
conn = conn_hash_lookup_ba(hdev, ACL_LINK, &rc->bdaddr); |
if (conn) { |
if (rc->role) |
conn->link_mode &= ~HCI_LM_MASTER; |
else |
conn->link_mode |= HCI_LM_MASTER; |
} |
|
hci_dev_unlock(hdev); |
} |
|
/* Authentication Complete */ |
static inline void hci_auth_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
evt_auth_complete *ac = (evt_auth_complete *) skb->data; |
struct hci_conn *conn = NULL; |
__u16 handle = __le16_to_cpu(ac->handle); |
|
BT_DBG("%s status %d", hdev->name, ac->status); |
|
hci_dev_lock(hdev); |
|
conn = conn_hash_lookup_handle(hdev, handle); |
if (conn) { |
if (!ac->status) |
conn->link_mode |= HCI_LM_AUTH; |
clear_bit(HCI_CONN_AUTH_PEND, &conn->pend); |
|
hci_proto_auth_cfm(conn, ac->status); |
|
if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) { |
if (!ac->status) { |
set_conn_encrypt_cp ce; |
ce.handle = __cpu_to_le16(conn->handle); |
ce.encrypt = 1; |
hci_send_cmd(conn->hdev, OGF_LINK_CTL, |
OCF_SET_CONN_ENCRYPT, |
SET_CONN_ENCRYPT_CP_SIZE, &ce); |
} else { |
clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend); |
hci_proto_encrypt_cfm(conn, ac->status); |
} |
} |
} |
|
hci_dev_unlock(hdev); |
} |
|
/* Encryption Change */ |
static inline void hci_encrypt_change_evt(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
evt_encrypt_change *ec = (evt_encrypt_change *) skb->data; |
struct hci_conn *conn = NULL; |
__u16 handle = __le16_to_cpu(ec->handle); |
|
BT_DBG("%s status %d", hdev->name, ec->status); |
|
hci_dev_lock(hdev); |
|
conn = conn_hash_lookup_handle(hdev, handle); |
if (conn) { |
if (!ec->status) { |
if (ec->encrypt) |
conn->link_mode |= HCI_LM_ENCRYPT; |
else |
conn->link_mode &= ~HCI_LM_ENCRYPT; |
} |
clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend); |
|
hci_proto_encrypt_cfm(conn, ec->status); |
} |
|
hci_dev_unlock(hdev); |
} |
|
void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
hci_event_hdr *he = (hci_event_hdr *) skb->data; |
evt_cmd_status *cs; |
evt_cmd_complete *ec; |
__u16 opcode, ocf, ogf; |
|
skb_pull(skb, HCI_EVENT_HDR_SIZE); |
|
BT_DBG("%s evt 0x%x", hdev->name, he->evt); |
|
switch (he->evt) { |
case EVT_NUM_COMP_PKTS: |
hci_num_comp_pkts_evt(hdev, skb); |
break; |
|
case EVT_INQUIRY_COMPLETE: |
hci_inquiry_complete_evt(hdev, skb); |
break; |
|
case EVT_INQUIRY_RESULT: |
hci_inquiry_result_evt(hdev, skb); |
break; |
|
case EVT_INQUIRY_RESULT_WITH_RSSI: |
hci_inquiry_result_with_rssi_evt(hdev, skb); |
break; |
|
case EVT_CONN_REQUEST: |
hci_conn_request_evt(hdev, skb); |
break; |
|
case EVT_CONN_COMPLETE: |
hci_conn_complete_evt(hdev, skb); |
break; |
|
case EVT_DISCONN_COMPLETE: |
hci_disconn_complete_evt(hdev, skb); |
break; |
|
case EVT_ROLE_CHANGE: |
hci_role_change_evt(hdev, skb); |
break; |
|
case EVT_AUTH_COMPLETE: |
hci_auth_complete_evt(hdev, skb); |
break; |
|
case EVT_ENCRYPT_CHANGE: |
hci_encrypt_change_evt(hdev, skb); |
break; |
|
case EVT_CMD_STATUS: |
cs = (evt_cmd_status *) skb->data; |
skb_pull(skb, EVT_CMD_STATUS_SIZE); |
|
opcode = __le16_to_cpu(cs->opcode); |
ogf = cmd_opcode_ogf(opcode); |
ocf = cmd_opcode_ocf(opcode); |
|
switch (ogf) { |
case OGF_INFO_PARAM: |
hci_cs_info_param(hdev, ocf, cs->status); |
break; |
|
case OGF_HOST_CTL: |
hci_cs_host_ctl(hdev, ocf, cs->status); |
break; |
|
case OGF_LINK_CTL: |
hci_cs_link_ctl(hdev, ocf, cs->status); |
break; |
|
case OGF_LINK_POLICY: |
hci_cs_link_policy(hdev, ocf, cs->status); |
break; |
|
default: |
BT_DBG("%s Command Status OGF %x", hdev->name, ogf); |
break; |
}; |
|
if (cs->ncmd) { |
atomic_set(&hdev->cmd_cnt, 1); |
if (!skb_queue_empty(&hdev->cmd_q)) |
hci_sched_cmd(hdev); |
} |
break; |
|
case EVT_CMD_COMPLETE: |
ec = (evt_cmd_complete *) skb->data; |
skb_pull(skb, EVT_CMD_COMPLETE_SIZE); |
|
opcode = __le16_to_cpu(ec->opcode); |
ogf = cmd_opcode_ogf(opcode); |
ocf = cmd_opcode_ocf(opcode); |
|
switch (ogf) { |
case OGF_INFO_PARAM: |
hci_cc_info_param(hdev, ocf, skb); |
break; |
|
case OGF_HOST_CTL: |
hci_cc_host_ctl(hdev, ocf, skb); |
break; |
|
case OGF_LINK_CTL: |
hci_cc_link_ctl(hdev, ocf, skb); |
break; |
|
case OGF_LINK_POLICY: |
hci_cc_link_policy(hdev, ocf, skb); |
break; |
|
default: |
BT_DBG("%s Command Completed OGF %x", hdev->name, ogf); |
break; |
}; |
|
if (ec->ncmd) { |
atomic_set(&hdev->cmd_cnt, 1); |
if (!skb_queue_empty(&hdev->cmd_q)) |
hci_sched_cmd(hdev); |
} |
break; |
}; |
|
kfree_skb(skb); |
hdev->stat.evt_rx++; |
} |
|
/* General internal stack event */ |
void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data) |
{ |
hci_event_hdr *eh; |
evt_stack_internal *si; |
struct sk_buff *skb; |
int size; |
void *ptr; |
|
size = HCI_EVENT_HDR_SIZE + EVT_STACK_INTERNAL_SIZE + dlen; |
skb = bluez_skb_alloc(size, GFP_ATOMIC); |
if (!skb) |
return; |
|
ptr = skb_put(skb, size); |
|
eh = ptr; |
eh->evt = EVT_STACK_INTERNAL; |
eh->plen = EVT_STACK_INTERNAL_SIZE + dlen; |
ptr += HCI_EVENT_HDR_SIZE; |
|
si = ptr; |
si->type = type; |
memcpy(si->data, data, dlen); |
|
skb->pkt_type = HCI_EVENT_PKT; |
skb->dev = (void *) hdev; |
hci_send_to_sock(hdev, skb); |
kfree_skb(skb); |
} |
/lib.c
0,0 → 1,175
/* |
BlueZ - Bluetooth protocol stack for Linux |
Copyright (C) 2000-2001 Qualcomm Incorporated |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* BlueZ kernel library. |
* |
* $Id: lib.c,v 1.1.1.1 2004-04-15 01:17:00 phoenix Exp $ |
*/ |
|
#include <linux/kernel.h> |
#include <linux/stddef.h> |
#include <linux/string.h> |
#include <asm/errno.h> |
|
#include <net/bluetooth/bluetooth.h> |
|
void bluez_dump(char *pref, __u8 *buf, int count) |
{ |
char *ptr; |
char line[100]; |
int i; |
|
printk(KERN_INFO "%s: dump, len %d\n", pref, count); |
|
ptr = line; |
*ptr = 0; |
for (i = 0; i<count; i++) { |
ptr += sprintf(ptr, " %2.2X", buf[i]); |
|
if (i && !((i + 1) % 20)) { |
printk(KERN_INFO "%s:%s\n", pref, line); |
ptr = line; |
*ptr = 0; |
} |
} |
|
if (line[0]) |
printk(KERN_INFO "%s:%s\n", pref, line); |
} |
|
void baswap(bdaddr_t *dst, bdaddr_t *src) |
{ |
unsigned char *d = (unsigned char *) dst; |
unsigned char *s = (unsigned char *) src; |
int i; |
|
for (i = 0; i < 6; i++) |
d[i] = s[5 - i]; |
} |
|
char *batostr(bdaddr_t *ba) |
{ |
static char str[2][18]; |
static int i = 1; |
|
i ^= 1; |
sprintf(str[i], "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", |
ba->b[0], ba->b[1], ba->b[2], |
ba->b[3], ba->b[4], ba->b[5]); |
|
return str[i]; |
} |
|
/* Bluetooth error codes to Unix errno mapping */ |
int bterr(__u16 code) |
{ |
switch (code) { |
case 0: |
return 0; |
|
case 0x01: |
return EBADRQC; |
|
case 0x02: |
return ENOTCONN; |
|
case 0x03: |
return EIO; |
|
case 0x04: |
return EHOSTDOWN; |
|
case 0x05: |
return EACCES; |
|
case 0x06: |
return EBADE; |
|
case 0x07: |
return ENOMEM; |
|
case 0x08: |
return ETIMEDOUT; |
|
case 0x09: |
return EMLINK; |
|
case 0x0a: |
return EMLINK; |
|
case 0x0b: |
return EALREADY; |
|
case 0x0c: |
return EBUSY; |
|
case 0x0d: |
case 0x0e: |
case 0x0f: |
return ECONNREFUSED; |
|
case 0x10: |
return ETIMEDOUT; |
|
case 0x11: |
case 0x27: |
case 0x29: |
case 0x20: |
return EOPNOTSUPP; |
|
case 0x12: |
return EINVAL; |
|
case 0x13: |
case 0x14: |
case 0x15: |
return ECONNRESET; |
|
case 0x16: |
return ECONNABORTED; |
|
case 0x17: |
return ELOOP; |
|
case 0x18: |
return EACCES; |
|
case 0x1a: |
return EPROTONOSUPPORT; |
|
case 0x1b: |
return ECONNREFUSED; |
|
case 0x19: |
case 0x1e: |
case 0x23: |
case 0x24: |
case 0x25: |
return EPROTO; |
|
default: |
return ENOSYS; |
}; |
} |
/Config.in
0,0 → 1,22
# |
# Bluetooth subsystem configuration |
# |
|
if [ "$CONFIG_NET" != "n" ]; then |
|
mainmenu_option next_comment |
comment 'Bluetooth support' |
dep_tristate 'Bluetooth subsystem support' CONFIG_BLUEZ $CONFIG_NET |
|
if [ "$CONFIG_BLUEZ" != "n" ]; then |
dep_tristate 'L2CAP protocol support' CONFIG_BLUEZ_L2CAP $CONFIG_BLUEZ |
dep_tristate 'SCO links support' CONFIG_BLUEZ_SCO $CONFIG_BLUEZ |
source net/bluetooth/rfcomm/Config.in |
source net/bluetooth/bnep/Config.in |
source net/bluetooth/cmtp/Config.in |
source drivers/bluetooth/Config.in |
fi |
|
endmenu |
fi |
|
/syms.c
0,0 → 1,81
/* |
BlueZ - Bluetooth protocol stack for Linux |
Copyright (C) 2000-2001 Qualcomm Incorporated |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* BlueZ symbols. |
* |
* $Id: syms.c,v 1.1.1.1 2004-04-15 01:17:03 phoenix Exp $ |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/init.h> |
#include <linux/skbuff.h> |
#include <linux/socket.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/hci_core.h> |
|
/* HCI Core */ |
EXPORT_SYMBOL(hci_register_dev); |
EXPORT_SYMBOL(hci_unregister_dev); |
EXPORT_SYMBOL(hci_suspend_dev); |
EXPORT_SYMBOL(hci_resume_dev); |
|
EXPORT_SYMBOL(hci_register_proto); |
EXPORT_SYMBOL(hci_unregister_proto); |
|
EXPORT_SYMBOL(hci_get_route); |
EXPORT_SYMBOL(hci_connect); |
EXPORT_SYMBOL(hci_dev_get); |
EXPORT_SYMBOL(hci_conn_auth); |
EXPORT_SYMBOL(hci_conn_encrypt); |
|
EXPORT_SYMBOL(hci_recv_frame); |
EXPORT_SYMBOL(hci_send_acl); |
EXPORT_SYMBOL(hci_send_sco); |
EXPORT_SYMBOL(hci_send_cmd); |
EXPORT_SYMBOL(hci_si_event); |
|
/* BlueZ lib */ |
EXPORT_SYMBOL(bluez_dump); |
EXPORT_SYMBOL(baswap); |
EXPORT_SYMBOL(batostr); |
EXPORT_SYMBOL(bterr); |
|
/* BlueZ sockets */ |
EXPORT_SYMBOL(bluez_sock_register); |
EXPORT_SYMBOL(bluez_sock_unregister); |
EXPORT_SYMBOL(bluez_sock_init); |
EXPORT_SYMBOL(bluez_sock_link); |
EXPORT_SYMBOL(bluez_sock_unlink); |
EXPORT_SYMBOL(bluez_sock_recvmsg); |
EXPORT_SYMBOL(bluez_sock_poll); |
EXPORT_SYMBOL(bluez_accept_enqueue); |
EXPORT_SYMBOL(bluez_accept_dequeue); |
EXPORT_SYMBOL(bluez_sock_wait_state); |
/hci_core.c
0,0 → 1,1410
/* |
BlueZ - Bluetooth protocol stack for Linux |
Copyright (C) 2000-2001 Qualcomm Incorporated |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* BlueZ HCI Core. |
* |
* $Id: hci_core.c,v 1.1.1.1 2004-04-15 01:17:05 phoenix Exp $ |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/kmod.h> |
|
#include <linux/types.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/fcntl.h> |
#include <linux/init.h> |
#include <linux/skbuff.h> |
#include <linux/interrupt.h> |
#include <linux/notifier.h> |
#include <net/sock.h> |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
#include <asm/unaligned.h> |
|
#include <net/bluetooth/bluetooth.h> |
#include <net/bluetooth/hci_core.h> |
|
#ifndef HCI_CORE_DEBUG |
#undef BT_DBG |
#define BT_DBG( A... ) |
#endif |
|
static void hci_cmd_task(unsigned long arg); |
static void hci_rx_task(unsigned long arg); |
static void hci_tx_task(unsigned long arg); |
static void hci_notify(struct hci_dev *hdev, int event); |
|
rwlock_t hci_task_lock = RW_LOCK_UNLOCKED; |
|
/* HCI device list */ |
LIST_HEAD(hdev_list); |
rwlock_t hdev_list_lock = RW_LOCK_UNLOCKED; |
|
/* HCI protocols */ |
#define HCI_MAX_PROTO 2 |
struct hci_proto *hci_proto[HCI_MAX_PROTO]; |
|
/* HCI notifiers list */ |
static struct notifier_block *hci_notifier; |
|
|
/* ---- HCI notifications ---- */ |
|
int hci_register_notifier(struct notifier_block *nb) |
{ |
return notifier_chain_register(&hci_notifier, nb); |
} |
|
int hci_unregister_notifier(struct notifier_block *nb) |
{ |
return notifier_chain_unregister(&hci_notifier, nb); |
} |
|
void hci_notify(struct hci_dev *hdev, int event) |
{ |
notifier_call_chain(&hci_notifier, event, hdev); |
} |
|
/* ---- HCI hotplug support ---- */ |
|
#ifdef CONFIG_HOTPLUG |
|
static int hci_run_hotplug(char *dev, char *action) |
{ |
char *argv[3], *envp[5], dstr[20], astr[32]; |
|
sprintf(dstr, "DEVICE=%s", dev); |
sprintf(astr, "ACTION=%s", action); |
|
argv[0] = hotplug_path; |
argv[1] = "bluetooth"; |
argv[2] = NULL; |
|
envp[0] = "HOME=/"; |
envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; |
envp[2] = dstr; |
envp[3] = astr; |
envp[4] = NULL; |
|
return call_usermodehelper(argv[0], argv, envp); |
} |
#else |
#define hci_run_hotplug(A...) |
#endif |
|
/* ---- HCI requests ---- */ |
|
void hci_req_complete(struct hci_dev *hdev, int result) |
{ |
BT_DBG("%s result 0x%2.2x", hdev->name, result); |
|
if (hdev->req_status == HCI_REQ_PEND) { |
hdev->req_result = result; |
hdev->req_status = HCI_REQ_DONE; |
wake_up_interruptible(&hdev->req_wait_q); |
} |
} |
|
void hci_req_cancel(struct hci_dev *hdev, int err) |
{ |
BT_DBG("%s err 0x%2.2x", hdev->name, err); |
|
if (hdev->req_status == HCI_REQ_PEND) { |
hdev->req_result = err; |
hdev->req_status = HCI_REQ_CANCELED; |
wake_up_interruptible(&hdev->req_wait_q); |
} |
} |
|
/* Execute request and wait for completion. */ |
static int __hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt), unsigned long opt, __u32 timeout) |
{ |
DECLARE_WAITQUEUE(wait, current); |
int err = 0; |
|
BT_DBG("%s start", hdev->name); |
|
hdev->req_status = HCI_REQ_PEND; |
|
add_wait_queue(&hdev->req_wait_q, &wait); |
set_current_state(TASK_INTERRUPTIBLE); |
|
req(hdev, opt); |
schedule_timeout(timeout); |
|
set_current_state(TASK_RUNNING); |
remove_wait_queue(&hdev->req_wait_q, &wait); |
|
if (signal_pending(current)) |
return -EINTR; |
|
switch (hdev->req_status) { |
case HCI_REQ_DONE: |
err = -bterr(hdev->req_result); |
break; |
|
case HCI_REQ_CANCELED: |
err = -hdev->req_result; |
break; |
|
default: |
err = -ETIMEDOUT; |
break; |
}; |
|
hdev->req_status = hdev->req_result = 0; |
|
BT_DBG("%s end: err %d", hdev->name, err); |
|
return err; |
} |
|
static inline int hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt), |
unsigned long opt, __u32 timeout) |
{ |
int ret; |
|
/* Serialize all requests */ |
hci_req_lock(hdev); |
ret = __hci_request(hdev, req, opt, timeout); |
hci_req_unlock(hdev); |
|
return ret; |
} |
|
static void hci_reset_req(struct hci_dev *hdev, unsigned long opt) |
{ |
BT_DBG("%s %ld", hdev->name, opt); |
|
/* Reset device */ |
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL); |
} |
|
static void hci_init_req(struct hci_dev *hdev, unsigned long opt) |
{ |
set_event_flt_cp ef; |
__u16 param; |
|
BT_DBG("%s %ld", hdev->name, opt); |
|
/* Mandatory initialization */ |
|
/* Reset */ |
if (test_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks)) |
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL); |
|
/* Read Local Supported Features */ |
hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_LOCAL_FEATURES, 0, NULL); |
|
/* Read Buffer Size (ACL mtu, max pkt, etc.) */ |
hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BUFFER_SIZE, 0, NULL); |
|
#if 0 |
/* Host buffer size */ |
{ |
host_buffer_size_cp bs; |
bs.acl_mtu = __cpu_to_le16(HCI_MAX_ACL_SIZE); |
bs.sco_mtu = HCI_MAX_SCO_SIZE; |
bs.acl_max_pkt = __cpu_to_le16(0xffff); |
bs.sco_max_pkt = __cpu_to_le16(0xffff); |
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_HOST_BUFFER_SIZE, |
HOST_BUFFER_SIZE_CP_SIZE, &bs); |
} |
#endif |
|
/* Read BD Address */ |
hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BD_ADDR, 0, NULL); |
|
/* Optional initialization */ |
|
/* Clear Event Filters */ |
ef.flt_type = FLT_CLEAR_ALL; |
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_SET_EVENT_FLT, 1, &ef); |
|
/* Page timeout ~20 secs */ |
param = __cpu_to_le16(0x8000); |
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_PG_TIMEOUT, 2, ¶m); |
|
/* Connection accept timeout ~20 secs */ |
param = __cpu_to_le16(0x7d00); |
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_CA_TIMEOUT, 2, ¶m); |
} |
|
static void hci_scan_req(struct hci_dev *hdev, unsigned long opt) |
{ |
__u8 scan = opt; |
|
BT_DBG("%s %x", hdev->name, scan); |
|
/* Inquiry and Page scans */ |
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, 1, &scan); |
} |
|
static void hci_auth_req(struct hci_dev *hdev, unsigned long opt) |
{ |
__u8 auth = opt; |
|
BT_DBG("%s %x", hdev->name, auth); |
|
/* Authentication */ |
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_AUTH_ENABLE, 1, &auth); |
} |
|
static void hci_encrypt_req(struct hci_dev *hdev, unsigned long opt) |
{ |
__u8 encrypt = opt; |
|
BT_DBG("%s %x", hdev->name, encrypt); |
|
/* Authentication */ |
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_ENCRYPT_MODE, 1, &encrypt); |
} |
|
/* Get HCI device by index. |
* Device is locked on return. */ |
struct hci_dev *hci_dev_get(int index) |
{ |
struct hci_dev *hdev; |
struct list_head *p; |
|
BT_DBG("%d", index); |
|
if (index < 0) |
return NULL; |
|
read_lock(&hdev_list_lock); |
list_for_each(p, &hdev_list) { |
hdev = list_entry(p, struct hci_dev, list); |
if (hdev->id == index) { |
hci_dev_hold(hdev); |
goto done; |
} |
} |
hdev = NULL; |
done: |
read_unlock(&hdev_list_lock); |
return hdev; |
} |
|
/* ---- Inquiry support ---- */ |
void inquiry_cache_flush(struct hci_dev *hdev) |
{ |
struct inquiry_cache *cache = &hdev->inq_cache; |
struct inquiry_entry *next = cache->list, *e; |
|
BT_DBG("cache %p", cache); |
|
cache->list = NULL; |
while ((e = next)) { |
next = e->next; |
kfree(e); |
} |
} |
|
struct inquiry_entry *inquiry_cache_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr) |
{ |
struct inquiry_cache *cache = &hdev->inq_cache; |
struct inquiry_entry *e; |
|
BT_DBG("cache %p, %s", cache, batostr(bdaddr)); |
|
for (e = cache->list; e; e = e->next) |
if (!bacmp(&e->info.bdaddr, bdaddr)) |
break; |
return e; |
} |
|
void inquiry_cache_update(struct hci_dev *hdev, inquiry_info *info) |
{ |
struct inquiry_cache *cache = &hdev->inq_cache; |
struct inquiry_entry *e; |
|
BT_DBG("cache %p, %s", cache, batostr(&info->bdaddr)); |
|
if (!(e = inquiry_cache_lookup(hdev, &info->bdaddr))) { |
/* Entry not in the cache. Add new one. */ |
if (!(e = kmalloc(sizeof(struct inquiry_entry), GFP_ATOMIC))) |
return; |
memset(e, 0, sizeof(struct inquiry_entry)); |
e->next = cache->list; |
cache->list = e; |
} |
|
memcpy(&e->info, info, sizeof(inquiry_info)); |
e->timestamp = jiffies; |
cache->timestamp = jiffies; |
} |
|
int inquiry_cache_dump(struct hci_dev *hdev, int num, __u8 *buf) |
{ |
struct inquiry_cache *cache = &hdev->inq_cache; |
inquiry_info *info = (inquiry_info *) buf; |
struct inquiry_entry *e; |
int copied = 0; |
|
for (e = cache->list; e && copied < num; e = e->next, copied++) |
memcpy(info++, &e->info, sizeof(inquiry_info)); |
|
BT_DBG("cache %p, copied %d", cache, copied); |
return copied; |
} |
|
static void hci_inq_req(struct hci_dev *hdev, unsigned long opt) |
{ |
struct hci_inquiry_req *ir = (struct hci_inquiry_req *) opt; |
inquiry_cp ic; |
|
BT_DBG("%s", hdev->name); |
|
if (test_bit(HCI_INQUIRY, &hdev->flags)) |
return; |
|
/* Start Inquiry */ |
memcpy(&ic.lap, &ir->lap, 3); |
ic.length = ir->length; |
ic.num_rsp = ir->num_rsp; |
hci_send_cmd(hdev, OGF_LINK_CTL, OCF_INQUIRY, INQUIRY_CP_SIZE, &ic); |
} |
|
int hci_inquiry(unsigned long arg) |
{ |
struct hci_inquiry_req ir; |
struct hci_dev *hdev; |
int err = 0, do_inquiry = 0, max_rsp; |
long timeo; |
__u8 *buf, *ptr; |
|
ptr = (void *) arg; |
if (copy_from_user(&ir, ptr, sizeof(ir))) |
return -EFAULT; |
|
if (!(hdev = hci_dev_get(ir.dev_id))) |
return -ENODEV; |
|
hci_dev_lock_bh(hdev); |
if (inquiry_cache_age(hdev) > INQUIRY_CACHE_AGE_MAX || |
inquiry_cache_empty(hdev) || |
ir.flags & IREQ_CACHE_FLUSH) { |
inquiry_cache_flush(hdev); |
do_inquiry = 1; |
} |
hci_dev_unlock_bh(hdev); |
|
timeo = ir.length * 2 * HZ; |
if (do_inquiry && (err = hci_request(hdev, hci_inq_req, (unsigned long)&ir, timeo)) < 0) |
goto done; |
|
/* for unlimited number of responses we will use buffer with 255 entries */ |
max_rsp = (ir.num_rsp == 0) ? 255 : ir.num_rsp; |
|
/* cache_dump can't sleep. Therefore we allocate temp buffer and then |
* copy it to the user space. |
*/ |
if (!(buf = kmalloc(sizeof(inquiry_info) * max_rsp, GFP_KERNEL))) { |
err = -ENOMEM; |
goto done; |
} |
|
hci_dev_lock_bh(hdev); |
ir.num_rsp = inquiry_cache_dump(hdev, max_rsp, buf); |
hci_dev_unlock_bh(hdev); |
|
BT_DBG("num_rsp %d", ir.num_rsp); |
|
if (!verify_area(VERIFY_WRITE, ptr, sizeof(ir) + |
(sizeof(inquiry_info) * ir.num_rsp))) { |
copy_to_user(ptr, &ir, sizeof(ir)); |
ptr += sizeof(ir); |
copy_to_user(ptr, buf, sizeof(inquiry_info) * ir.num_rsp); |
} else |
err = -EFAULT; |
|
kfree(buf); |
|
done: |
hci_dev_put(hdev); |
return err; |
} |
|
/* ---- HCI ioctl helpers ---- */ |
|
int hci_dev_open(__u16 dev) |
{ |
struct hci_dev *hdev; |
int ret = 0; |
|
if (!(hdev = hci_dev_get(dev))) |
return -ENODEV; |
|
BT_DBG("%s %p", hdev->name, hdev); |
|
hci_req_lock(hdev); |
|
if (test_bit(HCI_UP, &hdev->flags)) { |
ret = -EALREADY; |
goto done; |
} |
|
if (hdev->open(hdev)) { |
ret = -EIO; |
goto done; |
} |
|
if (!test_bit(HCI_RAW, &hdev->flags)) { |
atomic_set(&hdev->cmd_cnt, 1); |
set_bit(HCI_INIT, &hdev->flags); |
|
//__hci_request(hdev, hci_reset_req, 0, HZ); |
ret = __hci_request(hdev, hci_init_req, 0, HCI_INIT_TIMEOUT); |
|
clear_bit(HCI_INIT, &hdev->flags); |
} |
|
if (!ret) { |
set_bit(HCI_UP, &hdev->flags); |
hci_notify(hdev, HCI_DEV_UP); |
} else { |
/* Init failed, cleanup */ |
tasklet_kill(&hdev->rx_task); |
tasklet_kill(&hdev->tx_task); |
tasklet_kill(&hdev->cmd_task); |
|
skb_queue_purge(&hdev->cmd_q); |
skb_queue_purge(&hdev->rx_q); |
|
if (hdev->flush) |
hdev->flush(hdev); |
|
if (hdev->sent_cmd) { |
kfree_skb(hdev->sent_cmd); |
hdev->sent_cmd = NULL; |
} |
|
hdev->close(hdev); |
hdev->flags = 0; |
} |
|
done: |
hci_req_unlock(hdev); |
hci_dev_put(hdev); |
return ret; |
} |
|
static int hci_dev_do_close(struct hci_dev *hdev) |
{ |
BT_DBG("%s %p", hdev->name, hdev); |
|
hci_req_cancel(hdev, ENODEV); |
hci_req_lock(hdev); |
|
if (!test_and_clear_bit(HCI_UP, &hdev->flags)) { |
hci_req_unlock(hdev); |
return 0; |
} |
|
/* Kill RX and TX tasks */ |
tasklet_kill(&hdev->rx_task); |
tasklet_kill(&hdev->tx_task); |
|
hci_dev_lock_bh(hdev); |
inquiry_cache_flush(hdev); |
hci_conn_hash_flush(hdev); |
hci_dev_unlock_bh(hdev); |
|
hci_notify(hdev, HCI_DEV_DOWN); |
|
if (hdev->flush) |
hdev->flush(hdev); |
|
/* Reset device */ |
skb_queue_purge(&hdev->cmd_q); |
atomic_set(&hdev->cmd_cnt, 1); |
set_bit(HCI_INIT, &hdev->flags); |
__hci_request(hdev, hci_reset_req, 0, HZ/4); |
clear_bit(HCI_INIT, &hdev->flags); |
|
/* Kill cmd task */ |
tasklet_kill(&hdev->cmd_task); |
|
/* Drop queues */ |
skb_queue_purge(&hdev->rx_q); |
skb_queue_purge(&hdev->cmd_q); |
skb_queue_purge(&hdev->raw_q); |
|
/* Drop last sent command */ |
if (hdev->sent_cmd) { |
kfree_skb(hdev->sent_cmd); |
hdev->sent_cmd = NULL; |
} |
|
/* After this point our queues are empty |
* and no tasks are scheduled. */ |
hdev->close(hdev); |
|
/* Clear flags */ |
hdev->flags = 0; |
|
hci_req_unlock(hdev); |
return 0; |
} |
|
int hci_dev_close(__u16 dev) |
{ |
struct hci_dev *hdev; |
int err; |
|
if (!(hdev = hci_dev_get(dev))) |
return -ENODEV; |
err = hci_dev_do_close(hdev); |
hci_dev_put(hdev); |
return err; |
} |
|
int hci_dev_reset(__u16 dev) |
{ |
struct hci_dev *hdev; |
int ret = 0; |
|
if (!(hdev = hci_dev_get(dev))) |
return -ENODEV; |
|
hci_req_lock(hdev); |
tasklet_disable(&hdev->tx_task); |
|
if (!test_bit(HCI_UP, &hdev->flags)) |
goto done; |
|
/* Drop queues */ |
skb_queue_purge(&hdev->rx_q); |
skb_queue_purge(&hdev->cmd_q); |
|
hci_dev_lock_bh(hdev); |
inquiry_cache_flush(hdev); |
hci_conn_hash_flush(hdev); |
hci_dev_unlock_bh(hdev); |
|
if (hdev->flush) |
hdev->flush(hdev); |
|
atomic_set(&hdev->cmd_cnt, 1); |
hdev->acl_cnt = 0; hdev->sco_cnt = 0; |
|
ret = __hci_request(hdev, hci_reset_req, 0, HCI_INIT_TIMEOUT); |
|
done: |
tasklet_enable(&hdev->tx_task); |
hci_req_unlock(hdev); |
hci_dev_put(hdev); |
return ret; |
} |
|
int hci_dev_reset_stat(__u16 dev) |
{ |
struct hci_dev *hdev; |
int ret = 0; |
|
if (!(hdev = hci_dev_get(dev))) |
return -ENODEV; |
|
memset(&hdev->stat, 0, sizeof(struct hci_dev_stats)); |
|
hci_dev_put(hdev); |
|
return ret; |
} |
|
int hci_dev_cmd(unsigned int cmd, unsigned long arg) |
{ |
struct hci_dev *hdev; |
struct hci_dev_req dr; |
int err = 0; |
|
if (copy_from_user(&dr, (void *) arg, sizeof(dr))) |
return -EFAULT; |
|
if (!(hdev = hci_dev_get(dr.dev_id))) |
return -ENODEV; |
|
switch (cmd) { |
case HCISETAUTH: |
err = hci_request(hdev, hci_auth_req, dr.dev_opt, HCI_INIT_TIMEOUT); |
break; |
|
case HCISETENCRYPT: |
if (!lmp_encrypt_capable(hdev)) { |
err = -EOPNOTSUPP; |
break; |
} |
|
if (!test_bit(HCI_AUTH, &hdev->flags)) { |
/* Auth must be enabled first */ |
err = hci_request(hdev, hci_auth_req, |
dr.dev_opt, HCI_INIT_TIMEOUT); |
if (err) |
break; |
} |
|
err = hci_request(hdev, hci_encrypt_req, |
dr.dev_opt, HCI_INIT_TIMEOUT); |
break; |
|
case HCISETSCAN: |
err = hci_request(hdev, hci_scan_req, dr.dev_opt, HCI_INIT_TIMEOUT); |
break; |
|
case HCISETPTYPE: |
hdev->pkt_type = (__u16) dr.dev_opt; |
break; |
|
case HCISETLINKPOL: |
hdev->link_policy = (__u16) dr.dev_opt; |
break; |
|
case HCISETLINKMODE: |
hdev->link_mode = ((__u16) dr.dev_opt) & (HCI_LM_MASTER | HCI_LM_ACCEPT); |
break; |
|
case HCISETACLMTU: |
hdev->acl_mtu = *((__u16 *)&dr.dev_opt + 1); |
hdev->acl_pkts = *((__u16 *)&dr.dev_opt + 0); |
break; |
|
case HCISETSCOMTU: |
hdev->sco_mtu = *((__u16 *)&dr.dev_opt + 1); |
hdev->sco_pkts = *((__u16 *)&dr.dev_opt + 0); |
break; |
|
default: |
err = -EINVAL; |
break; |
} |
hci_dev_put(hdev); |
return err; |
} |
|
int hci_get_dev_list(unsigned long arg) |
{ |
struct hci_dev_list_req *dl; |
struct hci_dev_req *dr; |
struct list_head *p; |
int n = 0, size, err; |
__u16 dev_num; |
|
if (get_user(dev_num, (__u16 *) arg)) |
return -EFAULT; |
|
if (!dev_num || dev_num > (PAGE_SIZE * 2) / sizeof(*dr)) |
return -EINVAL; |
|
size = sizeof(*dl) + dev_num * sizeof(*dr); |
|
if (!(dl = kmalloc(size, GFP_KERNEL))) |
return -ENOMEM; |
|
dr = dl->dev_req; |
|
read_lock_bh(&hdev_list_lock); |
list_for_each(p, &hdev_list) { |
struct hci_dev *hdev; |
hdev = list_entry(p, struct hci_dev, list); |
(dr + n)->dev_id = hdev->id; |
(dr + n)->dev_opt = hdev->flags; |
if (++n >= dev_num) |
break; |
} |
read_unlock_bh(&hdev_list_lock); |
|
dl->dev_num = n; |
size = sizeof(*dl) + n * sizeof(*dr); |
|
err = copy_to_user((void *) arg, dl, size); |
kfree(dl); |
|
return err ? -EFAULT : 0; |
} |
|
int hci_get_dev_info(unsigned long arg) |
{ |
struct hci_dev *hdev; |
struct hci_dev_info di; |
int err = 0; |
|
if (copy_from_user(&di, (void *) arg, sizeof(di))) |
return -EFAULT; |
|
if (!(hdev = hci_dev_get(di.dev_id))) |
return -ENODEV; |
|
strcpy(di.name, hdev->name); |
di.bdaddr = hdev->bdaddr; |
di.type = hdev->type; |
di.flags = hdev->flags; |
di.pkt_type = hdev->pkt_type; |
di.acl_mtu = hdev->acl_mtu; |
di.acl_pkts = hdev->acl_pkts; |
di.sco_mtu = hdev->sco_mtu; |
di.sco_pkts = hdev->sco_pkts; |
di.link_policy = hdev->link_policy; |
di.link_mode = hdev->link_mode; |
|
memcpy(&di.stat, &hdev->stat, sizeof(di.stat)); |
memcpy(&di.features, &hdev->features, sizeof(di.features)); |
|
if (copy_to_user((void *) arg, &di, sizeof(di))) |
err = -EFAULT; |
|
hci_dev_put(hdev); |
|
return err; |
} |
|
|
/* ---- Interface to HCI drivers ---- */ |
|
/* Register HCI device */ |
int hci_register_dev(struct hci_dev *hdev) |
{ |
struct list_head *head = &hdev_list, *p; |
int id = 0; |
|
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type); |
|
if (!hdev->open || !hdev->close || !hdev->destruct) |
return -EINVAL; |
|
write_lock_bh(&hdev_list_lock); |
|
/* Find first available device id */ |
list_for_each(p, &hdev_list) { |
if (list_entry(p, struct hci_dev, list)->id != id) |
break; |
head = p; id++; |
} |
|
sprintf(hdev->name, "hci%d", id); |
hdev->id = id; |
list_add(&hdev->list, head); |
|
atomic_set(&hdev->refcnt, 1); |
spin_lock_init(&hdev->lock); |
|
hdev->flags = 0; |
hdev->pkt_type = (HCI_DM1 | HCI_DH1 | HCI_HV1); |
hdev->link_mode = (HCI_LM_ACCEPT); |
|
tasklet_init(&hdev->cmd_task, hci_cmd_task,(unsigned long) hdev); |
tasklet_init(&hdev->rx_task, hci_rx_task, (unsigned long) hdev); |
tasklet_init(&hdev->tx_task, hci_tx_task, (unsigned long) hdev); |
|
skb_queue_head_init(&hdev->rx_q); |
skb_queue_head_init(&hdev->cmd_q); |
skb_queue_head_init(&hdev->raw_q); |
|
init_waitqueue_head(&hdev->req_wait_q); |
init_MUTEX(&hdev->req_lock); |
|
inquiry_cache_init(hdev); |
|
conn_hash_init(hdev); |
|
memset(&hdev->stat, 0, sizeof(struct hci_dev_stats)); |
|
atomic_set(&hdev->promisc, 0); |
|
MOD_INC_USE_COUNT; |
|
write_unlock_bh(&hdev_list_lock); |
|
hci_notify(hdev, HCI_DEV_REG); |
hci_run_hotplug(hdev->name, "register"); |
|
return id; |
} |
|
/* Unregister HCI device */ |
int hci_unregister_dev(struct hci_dev *hdev) |
{ |
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type); |
|
write_lock_bh(&hdev_list_lock); |
list_del(&hdev->list); |
write_unlock_bh(&hdev_list_lock); |
|
hci_dev_do_close(hdev); |
|
hci_notify(hdev, HCI_DEV_UNREG); |
hci_run_hotplug(hdev->name, "unregister"); |
|
hci_dev_put(hdev); |
|
MOD_DEC_USE_COUNT; |
return 0; |
} |
|
/* Suspend HCI device */ |
int hci_suspend_dev(struct hci_dev *hdev) |
{ |
hci_notify(hdev, HCI_DEV_SUSPEND); |
hci_run_hotplug(hdev->name, "suspend"); |
return 0; |
} |
|
/* Resume HCI device */ |
int hci_resume_dev(struct hci_dev *hdev) |
{ |
hci_notify(hdev, HCI_DEV_RESUME); |
hci_run_hotplug(hdev->name, "resume"); |
return 0; |
} |
|
/* Receive frame from HCI drivers */ |
int hci_recv_frame(struct sk_buff *skb) |
{ |
struct hci_dev *hdev = (struct hci_dev *) skb->dev; |
|
if (!hdev || (!test_bit(HCI_UP, &hdev->flags) && |
!test_bit(HCI_INIT, &hdev->flags)) ) { |
kfree_skb(skb); |
return -1; |
} |
|
BT_DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); |
|
/* Incomming skb */ |
bluez_cb(skb)->incomming = 1; |
|
/* Time stamp */ |
do_gettimeofday(&skb->stamp); |
|
/* Queue frame for rx task */ |
skb_queue_tail(&hdev->rx_q, skb); |
hci_sched_rx(hdev); |
return 0; |
} |
|
/* ---- Interface to upper protocols ---- */ |
|
/* Register/Unregister protocols. |
* hci_task_lock is used to ensure that no tasks are running. */ |
int hci_register_proto(struct hci_proto *hp) |
{ |
int err = 0; |
|
BT_DBG("%p name %s id %d", hp, hp->name, hp->id); |
|
if (hp->id >= HCI_MAX_PROTO) |
return -EINVAL; |
|
write_lock_bh(&hci_task_lock); |
|
if (!hci_proto[hp->id]) |
hci_proto[hp->id] = hp; |
else |
err = -EEXIST; |
|
write_unlock_bh(&hci_task_lock); |
|
return err; |
} |
|
int hci_unregister_proto(struct hci_proto *hp) |
{ |
int err = 0; |
|
BT_DBG("%p name %s id %d", hp, hp->name, hp->id); |
|
if (hp->id >= HCI_MAX_PROTO) |
return -EINVAL; |
|
write_lock_bh(&hci_task_lock); |
|
if (hci_proto[hp->id]) |
hci_proto[hp->id] = NULL; |
else |
err = -ENOENT; |
|
write_unlock_bh(&hci_task_lock); |
|
return err; |
} |
|
static int hci_send_frame(struct sk_buff *skb) |
{ |
struct hci_dev *hdev = (struct hci_dev *) skb->dev; |
|
if (!hdev) { |
kfree_skb(skb); |
return -ENODEV; |
} |
|
BT_DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); |
|
if (atomic_read(&hdev->promisc)) { |
/* Time stamp */ |
do_gettimeofday(&skb->stamp); |
|
hci_send_to_sock(hdev, skb); |
} |
|
/* Get rid of skb owner, prior to sending to the driver. */ |
skb_orphan(skb); |
|
return hdev->send(skb); |
} |
|
/* Send HCI command */ |
int hci_send_cmd(struct hci_dev *hdev, __u16 ogf, __u16 ocf, __u32 plen, void *param) |
{ |
int len = HCI_COMMAND_HDR_SIZE + plen; |
hci_command_hdr *hc; |
struct sk_buff *skb; |
|
BT_DBG("%s ogf 0x%x ocf 0x%x plen %d", hdev->name, ogf, ocf, plen); |
|
if (!(skb = bluez_skb_alloc(len, GFP_ATOMIC))) { |
BT_ERR("%s Can't allocate memory for HCI command", hdev->name); |
return -ENOMEM; |
} |
|
hc = (hci_command_hdr *) skb_put(skb, HCI_COMMAND_HDR_SIZE); |
hc->opcode = __cpu_to_le16(cmd_opcode_pack(ogf, ocf)); |
hc->plen = plen; |
|
if (plen) |
memcpy(skb_put(skb, plen), param, plen); |
|
BT_DBG("skb len %d", skb->len); |
|
skb->pkt_type = HCI_COMMAND_PKT; |
skb->dev = (void *) hdev; |
skb_queue_tail(&hdev->cmd_q, skb); |
hci_sched_cmd(hdev); |
|
return 0; |
} |
|
/* Get data from the previously sent command */ |
void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 ogf, __u16 ocf) |
{ |
hci_command_hdr *hc; |
|
if (!hdev->sent_cmd) |
return NULL; |
|
hc = (void *) hdev->sent_cmd->data; |
|
if (hc->opcode != __cpu_to_le16(cmd_opcode_pack(ogf, ocf))) |
return NULL; |
|
BT_DBG("%s ogf 0x%x ocf 0x%x", hdev->name, ogf, ocf); |
|
return hdev->sent_cmd->data + HCI_COMMAND_HDR_SIZE; |
} |
|
/* Send ACL data */ |
static void hci_add_acl_hdr(struct sk_buff *skb, __u16 handle, __u16 flags) |
{ |
int len = skb->len; |
hci_acl_hdr *ah; |
|
ah = (hci_acl_hdr *) skb_push(skb, HCI_ACL_HDR_SIZE); |
ah->handle = __cpu_to_le16(acl_handle_pack(handle, flags)); |
ah->dlen = __cpu_to_le16(len); |
|
skb->h.raw = (void *) ah; |
} |
|
int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags) |
{ |
struct hci_dev *hdev = conn->hdev; |
struct sk_buff *list; |
|
BT_DBG("%s conn %p flags 0x%x", hdev->name, conn, flags); |
|
skb->dev = (void *) hdev; |
skb->pkt_type = HCI_ACLDATA_PKT; |
hci_add_acl_hdr(skb, conn->handle, flags | ACL_START); |
|
if (!(list = skb_shinfo(skb)->frag_list)) { |
/* Non fragmented */ |
BT_DBG("%s nonfrag skb %p len %d", hdev->name, skb, skb->len); |
|
skb_queue_tail(&conn->data_q, skb); |
} else { |
/* Fragmented */ |
BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len); |
|
skb_shinfo(skb)->frag_list = NULL; |
|
/* Queue all fragments atomically */ |
spin_lock_bh(&conn->data_q.lock); |
|
__skb_queue_tail(&conn->data_q, skb); |
do { |
skb = list; list = list->next; |
|
skb->dev = (void *) hdev; |
skb->pkt_type = HCI_ACLDATA_PKT; |
hci_add_acl_hdr(skb, conn->handle, flags | ACL_CONT); |
|
BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len); |
|
__skb_queue_tail(&conn->data_q, skb); |
} while (list); |
|
spin_unlock_bh(&conn->data_q.lock); |
} |
|
hci_sched_tx(hdev); |
return 0; |
} |
|
/* Send SCO data */ |
int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb) |
{ |
struct hci_dev *hdev = conn->hdev; |
hci_sco_hdr hs; |
|
BT_DBG("%s len %d", hdev->name, skb->len); |
|
if (skb->len > hdev->sco_mtu) { |
kfree_skb(skb); |
return -EINVAL; |
} |
|
hs.handle = __cpu_to_le16(conn->handle); |
hs.dlen = skb->len; |
|
skb->h.raw = skb_push(skb, HCI_SCO_HDR_SIZE); |
memcpy(skb->h.raw, &hs, HCI_SCO_HDR_SIZE); |
|
skb->dev = (void *) hdev; |
skb->pkt_type = HCI_SCODATA_PKT; |
skb_queue_tail(&conn->data_q, skb); |
hci_sched_tx(hdev); |
return 0; |
} |
|
/* ---- HCI TX task (outgoing data) ---- */ |
|
/* HCI Connection scheduler */ |
static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int *quote) |
{ |
struct conn_hash *h = &hdev->conn_hash; |
struct hci_conn *conn = NULL; |
int num = 0, min = ~0; |
struct list_head *p; |
|
/* We don't have to lock device here. Connections are always |
* added and removed with TX task disabled. */ |
list_for_each(p, &h->list) { |
struct hci_conn *c; |
c = list_entry(p, struct hci_conn, list); |
|
if (c->type != type || c->state != BT_CONNECTED |
|| skb_queue_empty(&c->data_q)) |
continue; |
num++; |
|
if (c->sent < min) { |
min = c->sent; |
conn = c; |
} |
} |
|
if (conn) { |
int cnt = (type == ACL_LINK ? hdev->acl_cnt : hdev->sco_cnt); |
int q = cnt / num; |
*quote = q ? q : 1; |
} else |
*quote = 0; |
|
BT_DBG("conn %p quote %d", conn, *quote); |
return conn; |
} |
|
static inline void hci_acl_tx_to(struct hci_dev *hdev) |
{ |
struct conn_hash *h = &hdev->conn_hash; |
struct list_head *p; |
struct hci_conn *c; |
|
BT_ERR("%s ACL tx timeout", hdev->name); |
|
/* Kill stalled connections */ |
list_for_each(p, &h->list) { |
c = list_entry(p, struct hci_conn, list); |
if (c->type == ACL_LINK && c->sent) { |
BT_ERR("%s killing stalled ACL connection %s", |
hdev->name, batostr(&c->dst)); |
hci_acl_disconn(c, 0x13); |
} |
} |
} |
|
static inline void hci_sched_acl(struct hci_dev *hdev) |
{ |
struct hci_conn *conn; |
struct sk_buff *skb; |
int quote; |
|
BT_DBG("%s", hdev->name); |
|
/* ACL tx timeout must be longer than maximum |
* link supervision timeout (40.9 seconds) */ |
if (!hdev->acl_cnt && (jiffies - hdev->acl_last_tx) > (HZ * 45)) |
hci_acl_tx_to(hdev); |
|
while (hdev->acl_cnt && (conn = hci_low_sent(hdev, ACL_LINK, "e))) { |
while (quote-- && (skb = skb_dequeue(&conn->data_q))) { |
BT_DBG("skb %p len %d", skb, skb->len); |
hci_send_frame(skb); |
hdev->acl_last_tx = jiffies; |
|
hdev->acl_cnt--; |
conn->sent++; |
} |
} |
} |
|
/* Schedule SCO */ |
static inline void hci_sched_sco(struct hci_dev *hdev) |
{ |
struct hci_conn *conn; |
struct sk_buff *skb; |
int quote; |
|
BT_DBG("%s", hdev->name); |
|
while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, "e))) { |
while (quote-- && (skb = skb_dequeue(&conn->data_q))) { |
BT_DBG("skb %p len %d", skb, skb->len); |
hci_send_frame(skb); |
|
conn->sent++; |
if (conn->sent == ~0) |
conn->sent = 0; |
} |
} |
} |
|
static void hci_tx_task(unsigned long arg) |
{ |
struct hci_dev *hdev = (struct hci_dev *) arg; |
struct sk_buff *skb; |
|
read_lock(&hci_task_lock); |
|
BT_DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt); |
|
/* Schedule queues and send stuff to HCI driver */ |
|
hci_sched_acl(hdev); |
|
hci_sched_sco(hdev); |
|
/* Send next queued raw (unknown type) packet */ |
while ((skb = skb_dequeue(&hdev->raw_q))) |
hci_send_frame(skb); |
|
read_unlock(&hci_task_lock); |
} |
|
|
/* ----- HCI RX task (incomming data proccessing) ----- */ |
|
/* ACL data packet */ |
static inline void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
hci_acl_hdr *ah = (void *) skb->data; |
struct hci_conn *conn; |
__u16 handle, flags; |
|
skb_pull(skb, HCI_ACL_HDR_SIZE); |
|
handle = __le16_to_cpu(ah->handle); |
flags = acl_flags(handle); |
handle = acl_handle(handle); |
|
BT_DBG("%s len %d handle 0x%x flags 0x%x", hdev->name, skb->len, handle, flags); |
|
hdev->stat.acl_rx++; |
|
hci_dev_lock(hdev); |
conn = conn_hash_lookup_handle(hdev, handle); |
hci_dev_unlock(hdev); |
|
if (conn) { |
register struct hci_proto *hp; |
|
/* Send to upper protocol */ |
if ((hp = hci_proto[HCI_PROTO_L2CAP]) && hp->recv_acldata) { |
hp->recv_acldata(conn, skb, flags); |
return; |
} |
} else { |
BT_ERR("%s ACL packet for unknown connection handle %d", |
hdev->name, handle); |
} |
|
kfree_skb(skb); |
} |
|
/* SCO data packet */ |
static inline void hci_scodata_packet(struct hci_dev *hdev, struct sk_buff *skb) |
{ |
hci_sco_hdr *sh = (void *) skb->data; |
struct hci_conn *conn; |
__u16 handle; |
|
skb_pull(skb, HCI_SCO_HDR_SIZE); |
|
handle = __le16_to_cpu(sh->handle); |
|
BT_DBG("%s len %d handle 0x%x", hdev->name, skb->len, handle); |
|
hdev->stat.sco_rx++; |
|
hci_dev_lock(hdev); |
conn = conn_hash_lookup_handle(hdev, handle); |
hci_dev_unlock(hdev); |
|
if (conn) { |
register struct hci_proto *hp; |
|
/* Send to upper protocol */ |
if ((hp = hci_proto[HCI_PROTO_SCO]) && hp->recv_scodata) { |
hp->recv_scodata(conn, skb); |
return; |
} |
} else { |
BT_ERR("%s SCO packet for unknown connection handle %d", |
hdev->name, handle); |
} |
|
kfree_skb(skb); |
} |
|
void hci_rx_task(unsigned long arg) |
{ |
struct hci_dev *hdev = (struct hci_dev *) arg; |
struct sk_buff *skb; |
|
BT_DBG("%s", hdev->name); |
|
read_lock(&hci_task_lock); |
|
while ((skb = skb_dequeue(&hdev->rx_q))) { |
if (atomic_read(&hdev->promisc)) { |
/* Send copy to the sockets */ |
hci_send_to_sock(hdev, skb); |
} |
|
if (test_bit(HCI_RAW, &hdev->flags)) { |
kfree_skb(skb); |
continue; |
} |
|
if (test_bit(HCI_INIT, &hdev->flags)) { |
/* Don't process data packets in this states. */ |
switch (skb->pkt_type) { |
case HCI_ACLDATA_PKT: |
case HCI_SCODATA_PKT: |
kfree_skb(skb); |
continue; |
}; |
} |
|
/* Process frame */ |
switch (skb->pkt_type) { |
case HCI_EVENT_PKT: |
hci_event_packet(hdev, skb); |
break; |
|
case HCI_ACLDATA_PKT: |
BT_DBG("%s ACL data packet", hdev->name); |
hci_acldata_packet(hdev, skb); |
break; |
|
case HCI_SCODATA_PKT: |
BT_DBG("%s SCO data packet", hdev->name); |
hci_scodata_packet(hdev, skb); |
break; |
|
default: |
kfree_skb(skb); |
break; |
} |
} |
|
read_unlock(&hci_task_lock); |
} |
|
static void hci_cmd_task(unsigned long arg) |
{ |
struct hci_dev *hdev = (struct hci_dev *) arg; |
struct sk_buff *skb; |
|
BT_DBG("%s cmd %d", hdev->name, atomic_read(&hdev->cmd_cnt)); |
|
if (!atomic_read(&hdev->cmd_cnt) && (jiffies - hdev->cmd_last_tx) > HZ) { |
BT_ERR("%s command tx timeout", hdev->name); |
atomic_set(&hdev->cmd_cnt, 1); |
} |
|
/* Send queued commands */ |
if (atomic_read(&hdev->cmd_cnt) && (skb = skb_dequeue(&hdev->cmd_q))) { |
if (hdev->sent_cmd) |
kfree_skb(hdev->sent_cmd); |
|
if ((hdev->sent_cmd = skb_clone(skb, GFP_ATOMIC))) { |
atomic_dec(&hdev->cmd_cnt); |
hci_send_frame(skb); |
hdev->cmd_last_tx = jiffies; |
} else { |
skb_queue_head(&hdev->cmd_q, skb); |
hci_sched_cmd(hdev); |
} |
} |
} |
|
/* ---- Initialization ---- */ |
|
int hci_core_init(void) |
{ |
return 0; |
} |
|
int hci_core_cleanup(void) |
{ |
return 0; |
} |
/af_bluetooth.c
0,0 → 1,336
/* |
BlueZ - Bluetooth protocol stack for Linux |
Copyright (C) 2000-2001 Qualcomm Incorporated |
|
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 |
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 |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, |
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS |
SOFTWARE IS DISCLAIMED. |
*/ |
|
/* |
* BlueZ Bluetooth address family and sockets. |
* |
* $Id: af_bluetooth.c,v 1.1.1.1 2004-04-15 01:17:06 phoenix Exp $ |
*/ |
#define VERSION "2.3" |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <linux/types.h> |
#include <linux/list.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/skbuff.h> |
#include <linux/init.h> |
#include <linux/poll.h> |
#include <linux/proc_fs.h> |
#include <net/sock.h> |
|
#if defined(CONFIG_KMOD) |
#include <linux/kmod.h> |
#endif |
|
#include <net/bluetooth/bluetooth.h> |
|
#ifndef AF_BLUETOOTH_DEBUG |
#undef BT_DBG |
#define BT_DBG( A... ) |
#endif |
|
/* Bluetooth sockets */ |
#define BLUEZ_MAX_PROTO 6 |
static struct net_proto_family *bluez_proto[BLUEZ_MAX_PROTO]; |
|
int bluez_sock_register(int proto, struct net_proto_family *ops) |
{ |
if (proto >= BLUEZ_MAX_PROTO) |
return -EINVAL; |
|
if (bluez_proto[proto]) |
return -EEXIST; |
|
bluez_proto[proto] = ops; |
return 0; |
} |
|
int bluez_sock_unregister(int proto) |
{ |
if (proto >= BLUEZ_MAX_PROTO) |
return -EINVAL; |
|
if (!bluez_proto[proto]) |
return -ENOENT; |
|
bluez_proto[proto] = NULL; |
return 0; |
} |
|
static int bluez_sock_create(struct socket *sock, int proto) |
{ |
if (proto >= BLUEZ_MAX_PROTO) |
return -EINVAL; |
|
#if defined(CONFIG_KMOD) |
if (!bluez_proto[proto]) { |
char module_name[30]; |
sprintf(module_name, "bt-proto-%d", proto); |
request_module(module_name); |
} |
#endif |
|
if (!bluez_proto[proto]) |
return -ENOENT; |
|
return bluez_proto[proto]->create(sock, proto); |
} |
|
void bluez_sock_init(struct socket *sock, struct sock *sk) |
{ |
sock_init_data(sock, sk); |
INIT_LIST_HEAD(&bluez_pi(sk)->accept_q); |
} |
|
void bluez_sock_link(struct bluez_sock_list *l, struct sock *sk) |
{ |
write_lock_bh(&l->lock); |
sk->next = l->head; |
l->head = sk; |
sock_hold(sk); |
write_unlock_bh(&l->lock); |
} |
|
void bluez_sock_unlink(struct bluez_sock_list *l, struct sock *sk) |
{ |
struct sock **skp; |
|
write_lock_bh(&l->lock); |
for (skp = &l->head; *skp; skp = &((*skp)->next)) { |
if (*skp == sk) { |
*skp = sk->next; |
__sock_put(sk); |
break; |
} |
} |
write_unlock_bh(&l->lock); |
} |
|
void bluez_accept_enqueue(struct sock *parent, struct sock *sk) |
{ |
BT_DBG("parent %p, sk %p", parent, sk); |
|
sock_hold(sk); |
list_add_tail(&bluez_pi(sk)->accept_q, &bluez_pi(parent)->accept_q); |
bluez_pi(sk)->parent = parent; |
parent->ack_backlog++; |
} |
|
static void bluez_accept_unlink(struct sock *sk) |
{ |
BT_DBG("sk %p state %d", sk, sk->state); |
|
list_del_init(&bluez_pi(sk)->accept_q); |
bluez_pi(sk)->parent->ack_backlog--; |
bluez_pi(sk)->parent = NULL; |
sock_put(sk); |
} |
|
struct sock *bluez_accept_dequeue(struct sock *parent, struct socket *newsock) |
{ |
struct list_head *p, *n; |
struct bluez_pinfo *pi; |
struct sock *sk; |
|
BT_DBG("parent %p", parent); |
|
list_for_each_safe(p, n, &bluez_pi(parent)->accept_q) { |
pi = list_entry(p, struct bluez_pinfo, accept_q); |
sk = bluez_sk(pi); |
|
lock_sock(sk); |
if (sk->state == BT_CLOSED) { |
release_sock(sk); |
bluez_accept_unlink(sk); |
continue; |
} |
|
if (sk->state == BT_CONNECTED || !newsock) { |
bluez_accept_unlink(sk); |
if (newsock) |
sock_graft(sk, newsock); |
release_sock(sk); |
return sk; |
} |
release_sock(sk); |
} |
return NULL; |
} |
|
int bluez_sock_recvmsg(struct socket *sock, struct msghdr *msg, int len, int flags, struct scm_cookie *scm) |
{ |
int noblock = flags & MSG_DONTWAIT; |
struct sock *sk = sock->sk; |
struct sk_buff *skb; |
int copied, err; |
|
BT_DBG("sock %p sk %p len %d", sock, sk, len); |
|
if (flags & (MSG_OOB)) |
return -EOPNOTSUPP; |
|
if (!(skb = skb_recv_datagram(sk, flags, noblock, &err))) { |
if (sk->shutdown & RCV_SHUTDOWN) |
return 0; |
return err; |
} |
|
msg->msg_namelen = 0; |
|
copied = skb->len; |
if (len < copied) { |
msg->msg_flags |= MSG_TRUNC; |
copied = len; |
} |
|
skb->h.raw = skb->data; |
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); |
|
skb_free_datagram(sk, skb); |
|
return err ? : copied; |
} |
|
unsigned int bluez_sock_poll(struct file * file, struct socket *sock, poll_table *wait) |
{ |
struct sock *sk = sock->sk; |
unsigned int mask = 0; |
|
BT_DBG("sock %p, sk %p", sock, sk); |
|
poll_wait(file, sk->sleep, wait); |
|
if (sk->err || !skb_queue_empty(&sk->error_queue)) |
mask |= POLLERR; |
|
if (sk->shutdown == SHUTDOWN_MASK) |
mask |= POLLHUP; |
|
if (!skb_queue_empty(&sk->receive_queue) || |
!list_empty(&bluez_pi(sk)->accept_q) || |
(sk->shutdown & RCV_SHUTDOWN)) |
mask |= POLLIN | POLLRDNORM; |
|
if (sk->state == BT_CLOSED) |
mask |= POLLHUP; |
|
if (sk->state == BT_CONNECT || |
sk->state == BT_CONNECT2 || |
sk->state == BT_CONFIG) |
return mask; |
|
if (sock_writeable(sk)) |
mask |= POLLOUT | POLLWRNORM | POLLWRBAND; |
else |
set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags); |
|
return mask; |
} |
|
int bluez_sock_wait_state(struct sock *sk, int state, unsigned long timeo) |
{ |
DECLARE_WAITQUEUE(wait, current); |
int err = 0; |
|
BT_DBG("sk %p", sk); |
|
add_wait_queue(sk->sleep, &wait); |
while (sk->state != state) { |
set_current_state(TASK_INTERRUPTIBLE); |
|
if (!timeo) { |
err = -EAGAIN; |
break; |
} |
|
if (signal_pending(current)) { |
err = sock_intr_errno(timeo); |
break; |
} |
|
release_sock(sk); |
timeo = schedule_timeout(timeo); |
lock_sock(sk); |
|
if (sk->err) { |
err = sock_error(sk); |
break; |
} |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(sk->sleep, &wait); |
return err; |
} |
|
struct net_proto_family bluez_sock_family_ops = |
{ |
PF_BLUETOOTH, bluez_sock_create |
}; |
|
int bluez_init(void) |
{ |
BT_INFO("BlueZ Core ver %s Copyright (C) 2000,2001 Qualcomm Inc", |
VERSION); |
BT_INFO("Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>"); |
|
proc_mkdir("bluetooth", NULL); |
|
sock_register(&bluez_sock_family_ops); |
|
/* Init HCI Core */ |
hci_core_init(); |
|
/* Init sockets */ |
hci_sock_init(); |
|
return 0; |
} |
|
void bluez_cleanup(void) |
{ |
/* Release socket */ |
hci_sock_cleanup(); |
|
/* Release core */ |
hci_core_cleanup(); |
|
sock_unregister(PF_BLUETOOTH); |
|
remove_proc_entry("bluetooth", NULL); |
} |
|
#ifdef MODULE |
module_init(bluez_init); |
module_exit(bluez_cleanup); |
|
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>"); |
MODULE_DESCRIPTION("BlueZ Core ver " VERSION); |
MODULE_LICENSE("GPL"); |
#endif |
/Makefile
0,0 → 1,31
# |
# Makefile for the Linux Bluetooth subsystem |
# |
|
O_TARGET := bluetooth.o |
|
list-multi := bluez.o |
export-objs := syms.o l2cap.o |
|
bluez-objs := af_bluetooth.o hci_core.o hci_conn.o hci_event.o hci_sock.o lib.o syms.o |
|
obj-$(CONFIG_BLUEZ) += bluez.o |
obj-$(CONFIG_BLUEZ_L2CAP) += l2cap.o |
obj-$(CONFIG_BLUEZ_SCO) += sco.o |
|
subdir-$(CONFIG_BLUEZ_RFCOMM) += rfcomm |
subdir-$(CONFIG_BLUEZ_BNEP) += bnep |
subdir-$(CONFIG_BLUEZ_CMTP) += cmtp |
|
ifeq ($(CONFIG_BLUEZ_RFCOMM),y) |
obj-y += rfcomm/rfcomm.o |
endif |
|
ifeq ($(CONFIG_BLUEZ_BNEP),y) |
obj-y += bnep/bnep.o |
endif |
|
include $(TOPDIR)/Rules.make |
|
bluez.o: $(bluez-objs) |
$(LD) -r -o $@ $(bluez-objs) |