OpenCores
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(&current->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(&current->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(&current->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, &param);
 
/* Connection accept timeout ~20 secs */
param = __cpu_to_le16(0x7d00);
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_CA_TIMEOUT, 2, &param);
}
 
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, &quote))) {
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, &quote))) {
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)

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.