Creation of Cybook 2416 (actually Gen4) repository

This commit is contained in:
mlt
2009-12-18 17:10:00 +00:00
committed by godzil
commit 76f20f4d40
13791 changed files with 6812321 additions and 0 deletions

63
net/bluetooth/Kconfig Normal file
View File

@@ -0,0 +1,63 @@
#
# Bluetooth subsystem configuration
#
menuconfig BT
depends on NET
tristate "Bluetooth subsystem support"
help
Bluetooth is low-cost, low-power, short-range wireless technology.
It was designed as a replacement for cables and other short-range
technologies like IrDA. Bluetooth operates in personal area range
that typically extends up to 10 meters. More information about
Bluetooth can be found at <http://www.bluetooth.com/>.
Linux Bluetooth subsystem consist of several layers:
Bluetooth Core (HCI device and connection manager, scheduler)
HCI Device drivers (Interface to the hardware)
SCO Module (SCO audio links)
L2CAP Module (Logical Link Control and Adaptation Protocol)
RFCOMM Module (RFCOMM Protocol)
BNEP Module (Bluetooth Network Encapsulation Protocol)
CMTP Module (CAPI Message Transport Protocol)
HIDP Module (Human Interface Device Protocol)
Say Y here to compile Bluetooth support into the kernel or say M to
compile it as module (bluetooth).
To use Linux Bluetooth subsystem, you will need several user-space
utilities like hciconfig and hcid. These utilities and updates to
Bluetooth kernel modules are provided in the BlueZ packages.
For more information, see <http://www.bluez.org/>.
config BT_L2CAP
tristate "L2CAP protocol support"
depends on BT
help
L2CAP (Logical Link Control and Adaptation Protocol) provides
connection oriented and connection-less data transport. L2CAP
support is required for most Bluetooth applications.
Say Y here to compile L2CAP support into the kernel or say M to
compile it as module (l2cap).
config BT_SCO
tristate "SCO links support"
depends on BT
help
SCO link provides voice transport over Bluetooth. SCO support is
required for voice applications like Headset and Audio.
Say Y here to compile SCO support into the kernel or say M to
compile it as module (sco).
source "net/bluetooth/rfcomm/Kconfig"
source "net/bluetooth/bnep/Kconfig"
source "net/bluetooth/cmtp/Kconfig"
source "net/bluetooth/hidp/Kconfig"
source "drivers/bluetooth/Kconfig"

13
net/bluetooth/Makefile Normal file
View File

@@ -0,0 +1,13 @@
#
# Makefile for the Linux Bluetooth subsystem.
#
obj-$(CONFIG_BT) += bluetooth.o
obj-$(CONFIG_BT_L2CAP) += l2cap.o
obj-$(CONFIG_BT_SCO) += sco.o
obj-$(CONFIG_BT_RFCOMM) += rfcomm/
obj-$(CONFIG_BT_BNEP) += bnep/
obj-$(CONFIG_BT_CMTP) += cmtp/
obj-$(CONFIG_BT_HIDP) += hidp/
bluetooth-objs := af_bluetooth.o hci_core.o hci_conn.o hci_event.o hci_sock.o hci_sysfs.o lib.o

View File

@@ -0,0 +1,369 @@
/*
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.
*/
/* Bluetooth address family and sockets. */
#include <linux/module.h>
#include <linux/types.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <net/sock.h>
#if defined(CONFIG_KMOD)
#include <linux/kmod.h>
#endif
#include <net/bluetooth/bluetooth.h>
#ifndef CONFIG_BT_SOCK_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
#define VERSION "2.11"
/* Bluetooth sockets */
#define BT_MAX_PROTO 8
static struct net_proto_family *bt_proto[BT_MAX_PROTO];
static DEFINE_RWLOCK(bt_proto_lock);
int bt_sock_register(int proto, struct net_proto_family *ops)
{
int err = 0;
if (proto < 0 || proto >= BT_MAX_PROTO)
return -EINVAL;
write_lock(&bt_proto_lock);
if (bt_proto[proto])
err = -EEXIST;
else
bt_proto[proto] = ops;
write_unlock(&bt_proto_lock);
return err;
}
EXPORT_SYMBOL(bt_sock_register);
int bt_sock_unregister(int proto)
{
int err = 0;
if (proto < 0 || proto >= BT_MAX_PROTO)
return -EINVAL;
write_lock(&bt_proto_lock);
if (!bt_proto[proto])
err = -ENOENT;
else
bt_proto[proto] = NULL;
write_unlock(&bt_proto_lock);
return err;
}
EXPORT_SYMBOL(bt_sock_unregister);
static int bt_sock_create(struct socket *sock, int proto)
{
int err;
if (proto < 0 || proto >= BT_MAX_PROTO)
return -EINVAL;
#if defined(CONFIG_KMOD)
if (!bt_proto[proto]) {
request_module("bt-proto-%d", proto);
}
#endif
err = -EPROTONOSUPPORT;
read_lock(&bt_proto_lock);
if (bt_proto[proto] && try_module_get(bt_proto[proto]->owner)) {
err = bt_proto[proto]->create(sock, proto);
module_put(bt_proto[proto]->owner);
}
read_unlock(&bt_proto_lock);
return err;
}
void bt_sock_link(struct bt_sock_list *l, struct sock *sk)
{
write_lock_bh(&l->lock);
sk_add_node(sk, &l->head);
write_unlock_bh(&l->lock);
}
EXPORT_SYMBOL(bt_sock_link);
void bt_sock_unlink(struct bt_sock_list *l, struct sock *sk)
{
write_lock_bh(&l->lock);
sk_del_node_init(sk);
write_unlock_bh(&l->lock);
}
EXPORT_SYMBOL(bt_sock_unlink);
void bt_accept_enqueue(struct sock *parent, struct sock *sk)
{
BT_DBG("parent %p, sk %p", parent, sk);
sock_hold(sk);
list_add_tail(&bt_sk(sk)->accept_q, &bt_sk(parent)->accept_q);
bt_sk(sk)->parent = parent;
parent->sk_ack_backlog++;
}
EXPORT_SYMBOL(bt_accept_enqueue);
void bt_accept_unlink(struct sock *sk)
{
BT_DBG("sk %p state %d", sk, sk->sk_state);
list_del_init(&bt_sk(sk)->accept_q);
bt_sk(sk)->parent->sk_ack_backlog--;
bt_sk(sk)->parent = NULL;
sock_put(sk);
}
EXPORT_SYMBOL(bt_accept_unlink);
struct sock *bt_accept_dequeue(struct sock *parent, struct socket *newsock)
{
struct list_head *p, *n;
struct sock *sk;
BT_DBG("parent %p", parent);
list_for_each_safe(p, n, &bt_sk(parent)->accept_q) {
sk = (struct sock *) list_entry(p, struct bt_sock, accept_q);
lock_sock(sk);
/* FIXME: Is this check still needed */
if (sk->sk_state == BT_CLOSED) {
release_sock(sk);
bt_accept_unlink(sk);
continue;
}
if (sk->sk_state == BT_CONNECTED || !newsock) {
bt_accept_unlink(sk);
if (newsock)
sock_graft(sk, newsock);
release_sock(sk);
return sk;
}
release_sock(sk);
}
return NULL;
}
EXPORT_SYMBOL(bt_accept_dequeue);
int bt_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t len, int flags)
{
int noblock = flags & MSG_DONTWAIT;
struct sock *sk = sock->sk;
struct sk_buff *skb;
size_t copied;
int 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->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;
}
EXPORT_SYMBOL(bt_sock_recvmsg);
static inline unsigned int bt_accept_poll(struct sock *parent)
{
struct list_head *p, *n;
struct sock *sk;
list_for_each_safe(p, n, &bt_sk(parent)->accept_q) {
sk = (struct sock *) list_entry(p, struct bt_sock, accept_q);
if (sk->sk_state == BT_CONNECTED)
return POLLIN | POLLRDNORM;
}
return 0;
}
unsigned int bt_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->sk_sleep, wait);
if (sk->sk_state == BT_LISTEN)
return bt_accept_poll(sk);
if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
mask |= POLLERR;
if (sk->sk_shutdown & RCV_SHUTDOWN)
mask |= POLLRDHUP;
if (sk->sk_shutdown == SHUTDOWN_MASK)
mask |= POLLHUP;
if (!skb_queue_empty(&sk->sk_receive_queue) ||
(sk->sk_shutdown & RCV_SHUTDOWN))
mask |= POLLIN | POLLRDNORM;
if (sk->sk_state == BT_CLOSED)
mask |= POLLHUP;
if (sk->sk_state == BT_CONNECT ||
sk->sk_state == BT_CONNECT2 ||
sk->sk_state == BT_CONFIG)
return mask;
if (sock_writeable(sk))
mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
else
set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
return mask;
}
EXPORT_SYMBOL(bt_sock_poll);
int bt_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->sk_sleep, &wait);
while (sk->sk_state != state) {
set_current_state(TASK_INTERRUPTIBLE);
if (!timeo) {
err = -EINPROGRESS;
break;
}
if (signal_pending(current)) {
err = sock_intr_errno(timeo);
break;
}
release_sock(sk);
timeo = schedule_timeout(timeo);
lock_sock(sk);
err = sock_error(sk);
if (err)
break;
}
set_current_state(TASK_RUNNING);
remove_wait_queue(sk->sk_sleep, &wait);
return err;
}
EXPORT_SYMBOL(bt_sock_wait_state);
static struct net_proto_family bt_sock_family_ops = {
.owner = THIS_MODULE,
.family = PF_BLUETOOTH,
.create = bt_sock_create,
};
static int __init bt_init(void)
{
int err;
BT_INFO("Core ver %s", VERSION);
err = bt_sysfs_init();
if (err < 0)
return err;
err = sock_register(&bt_sock_family_ops);
if (err < 0) {
bt_sysfs_cleanup();
return err;
}
BT_INFO("HCI device and connection manager initialized");
hci_sock_init();
return 0;
}
static void __exit bt_exit(void)
{
hci_sock_cleanup();
sock_unregister(PF_BLUETOOTH);
bt_sysfs_cleanup();
}
subsys_initcall(bt_init);
module_exit(bt_exit);
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Bluetooth Core ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");
MODULE_ALIAS_NETPROTO(PF_BLUETOOTH);

View File

@@ -0,0 +1,24 @@
config BT_BNEP
tristate "BNEP protocol support"
depends on BT && BT_L2CAP
select CRC32
help
BNEP (Bluetooth Network Encapsulation Protocol) is Ethernet
emulation layer on top of Bluetooth. BNEP is required for
Bluetooth PAN (Personal Area Network).
Say Y here to compile BNEP support into the kernel or say M to
compile it as module (bnep).
config BT_BNEP_MC_FILTER
bool "Multicast filter support"
depends on BT_BNEP
help
This option enables the multicast filter support for BNEP.
config BT_BNEP_PROTO_FILTER
bool "Protocol filter support"
depends on BT_BNEP
help
This option enables the protocol filter support for BNEP.

View File

@@ -0,0 +1,7 @@
#
# Makefile for the Linux Bluetooth BNEP layer.
#
obj-$(CONFIG_BT_BNEP) += bnep.o
bnep-objs := core.o sock.o netdev.o

184
net/bluetooth/bnep/bnep.h Normal file
View File

@@ -0,0 +1,184 @@
/*
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 2007/06/12 07:27:14 eyryu Exp $
*/
#ifndef _BNEP_H
#define _BNEP_H
#include <linux/types.h>
#include <linux/crc32.h>
#include <net/bluetooth/bluetooth.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;
__be16 len;
__u8 list[0];
} __attribute__((packed));
struct bnep_control_rsp {
__u8 type;
__u8 ctrl;
__be16 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 __user *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;
};
void bnep_net_setup(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

732
net/bluetooth/bnep/core.c Normal file
View File

@@ -0,0 +1,732 @@
/*
BNEP implementation for Linux Bluetooth stack (BlueZ).
Copyright (C) 2001-2002 Inventel Systemes
Written 2001-2002 by
Cl<43>ment Moreau <clement.moreau@inventel.fr>
David Libault <david.libault@inventel.fr>
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;
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 2007/06/12 07:27:14 eyryu Exp $
*/
#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/hci_core.h>
#include <net/bluetooth/l2cap.h>
#include "bnep.h"
#ifndef CONFIG_BT_BNEP_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
#define VERSION "1.2"
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 (!compare_ether_addr(dst, s->eh.h_source))
return s;
}
return NULL;
}
static void __bnep_link_session(struct bnep_session *s)
{
/* It's safe to call __module_get() here because sessions are added
by the socket layer which has to hold the refference to this module.
*/
__module_get(THIS_MODULE);
list_add(&s->list, &bnep_session_list);
}
static void __bnep_unlink_session(struct bnep_session *s)
{
list_del(&s->list);
module_put(THIS_MODULE);
}
static int bnep_send(struct bnep_session *s, void *data, size_t len)
{
struct socket *sock = s->sock;
struct kvec iv = { data, len };
return kernel_sendmsg(sock, &s->msg, &iv, 1, len);
}
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));
}
#ifdef CONFIG_BT_BNEP_PROTO_FILTER
static inline void bnep_set_default_proto_filter(struct bnep_session *s)
{
/* (IPv4, ARP) */
s->proto_filter[0].start = ETH_P_IP;
s->proto_filter[0].end = ETH_P_ARP;
/* (RARP, AppleTalk) */
s->proto_filter[1].start = ETH_P_RARP;
s->proto_filter[1].end = ETH_P_AARP;
/* (IPX, IPv6) */
s->proto_filter[2].start = ETH_P_IPX;
s->proto_filter[2].end = ETH_P_IPV6;
}
#endif
static int bnep_ctrl_set_netfilter(struct bnep_session *s, __be16 *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_BT_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 = ntohs(get_unaligned(data++));
f[i].end = ntohs(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));
if (n == 0)
bnep_set_default_proto_filter(s);
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((__be16 *) data));
data += 2; len -= 2;
if (len < n)
return -EILSEQ;
BT_DBG("filter len %d", n);
#ifdef CONFIG_BT_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), (ulong *) &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), (ulong *) &s->mc_filter);
while (memcmp(a1, a2, 6) < 0 && s->mc_filter != ~0LL) {
INCA(a1);
set_bit(bnep_mc_hash(a1), (ulong *) &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((__be16 *) (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((__be16 *) (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, (__be16 *) __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, (__be16 *) __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_NONE;
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 kvec 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 kvec) { &type, 1 };
len++;
if (!compare_ether_addr(eh->h_dest, s->eh.h_source))
type |= 0x01;
if (!compare_ether_addr(eh->h_source, s->eh.h_dest))
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 kvec) { eh->h_source, ETH_ALEN };
len += ETH_ALEN;
break;
case BNEP_COMPRESSED_DST_ONLY:
iv[il++] = (struct kvec) { eh->h_dest, ETH_ALEN };
len += ETH_ALEN;
break;
}
send:
iv[il++] = (struct kvec) { skb->data, skb->len };
len += skb->len;
/* FIXME: linearize skb */
{
len = kernel_sendmsg(sock, &s->msg, iv, il, len);
}
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("kbnepd %s", dev->name);
set_user_nice(current, -15);
current->flags |= PF_NOFREEZE;
init_waitqueue_entry(&wait, current);
add_wait_queue(sk->sk_sleep, &wait);
while (!atomic_read(&s->killed)) {
set_current_state(TASK_INTERRUPTIBLE);
// RX
while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
skb_orphan(skb);
bnep_rx_frame(s, skb);
}
if (sk->sk_state != BT_CONNECTED)
break;
// TX
while ((skb = skb_dequeue(&sk->sk_write_queue)))
if (bnep_tx_frame(s, skb))
break;
netif_wake_queue(dev);
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(sk->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);
free_netdev(dev);
return 0;
}
static struct device *bnep_get_device(struct bnep_session *session)
{
bdaddr_t *src = &bt_sk(session->sock->sk)->src;
bdaddr_t *dst = &bt_sk(session->sock->sk)->dst;
struct hci_dev *hdev;
struct hci_conn *conn;
hdev = hci_get_route(dst, src);
if (!hdev)
return NULL;
conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst);
hci_dev_put(hdev);
return conn ? &conn->dev : NULL;
}
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, &bt_sk(sock->sk)->dst);
baswap((void *) src, &bt_sk(sock->sk)->src);
/* session struct allocated as private part of net_device */
dev = alloc_netdev(sizeof(struct bnep_session),
(*req->device) ? req->device : "bnep%d",
bnep_net_setup);
if (!dev)
return -ENOMEM;
down_write(&bnep_session_sem);
ss = __bnep_get_session(dst);
if (ss && ss->state == BT_CONNECTED) {
err = -EEXIST;
goto failed;
}
s = dev->priv;
/* This is rx header therefore addresses are swapped.
* ie eh.h_dest is our local address. */
memcpy(s->eh.h_dest, &src, ETH_ALEN);
memcpy(s->eh.h_source, &dst, ETH_ALEN);
memcpy(dev->dev_addr, s->eh.h_dest, ETH_ALEN);
s->dev = dev;
s->sock = sock;
s->role = req->role;
s->state = BT_CONNECTED;
s->msg.msg_flags = MSG_NOSIGNAL;
#ifdef CONFIG_BT_BNEP_MC_FILTER
/* Set default mc filter */
set_bit(bnep_mc_hash(dev->broadcast), (ulong *) &s->mc_filter);
#endif
#ifdef CONFIG_BT_BNEP_PROTO_FILTER
/* Set default protocol filter */
bnep_set_default_proto_filter(s);
#endif
SET_NETDEV_DEV(dev, bnep_get_device(s));
err = register_netdev(dev);
if (err) {
goto failed;
}
__bnep_link_session(s);
err = kernel_thread(bnep_session, s, CLONE_KERNEL);
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);
free_netdev(dev);
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->sk_err = EUNATCH;
/* Kill session thread */
atomic_inc(&s->killed);
wake_up_interruptible(s->sock->sk->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(void)
{
char flt[50] = "";
l2cap_load();
#ifdef CONFIG_BT_BNEP_PROTO_FILTER
strcat(flt, "protocol ");
#endif
#ifdef CONFIG_BT_BNEP_MC_FILTER
strcat(flt, "multicast");
#endif
BT_INFO("BNEP (Ethernet Emulation) ver %s", VERSION);
if (flt[0])
BT_INFO("BNEP filters: %s", flt);
bnep_sock_init();
return 0;
}
static void __exit bnep_exit(void)
{
bnep_sock_cleanup();
}
module_init(bnep_init);
module_exit(bnep_exit);
MODULE_AUTHOR("David Libault <david.libault@inventel.fr>, Maxim Krasnyansky <maxk@qualcomm.com>");
MODULE_DESCRIPTION("Bluetooth BNEP ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");
MODULE_ALIAS("bt-proto-4");

247
net/bluetooth/bnep/netdev.c Normal file
View File

@@ -0,0 +1,247 @@
/*
BNEP implementation for Linux Bluetooth stack (BlueZ).
Copyright (C) 2001-2002 Inventel Systemes
Written 2001-2002 by
Cl<43>ment Moreau <clement.moreau@inventel.fr>
David Libault <david.libault@inventel.fr>
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;
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 2007/06/12 07:27:14 eyryu Exp $
*/
#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_BT_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_BT_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->sk_write_queue, skb);
wake_up_interruptible(sk->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_BT_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), (ulong *) &s->mc_filter))
return 1;
return 0;
}
#endif
#ifdef CONFIG_BT_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;
u16 proto = ntohs(eh->h_proto);
if (proto >= 1536)
return proto;
if (get_unaligned((__be16 *) skb->data) == htons(0xFFFF))
return ETH_P_802_3;
return 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_BT_BNEP_MC_FILTER
if (bnep_net_mc_filter(skb, s)) {
kfree_skb(skb);
return 0;
}
#endif
#ifdef CONFIG_BT_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->sk_sleep.
*/
dev->trans_start = jiffies;
skb_queue_tail(&sk->sk_write_queue, skb);
wake_up_interruptible(sk->sk_sleep);
if (skb_queue_len(&sk->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;
}
void bnep_net_setup(struct net_device *dev)
{
memset(dev->broadcast, 0xff, 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;
}

268
net/bluetooth/bnep/sock.c Normal file
View File

@@ -0,0 +1,268 @@
/*
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 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.
*/
/*
* $Id: sock.c,v 1.1.1.1 2007/06/12 07:27:14 eyryu Exp $
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/kernel.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 <linux/compat.h>
#include <net/sock.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include "bnep.h"
#ifndef CONFIG_BT_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);
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;
void __user *argp = (void __user *)arg;
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, argp, sizeof(ca)))
return -EFAULT;
nsock = sockfd_lookup(ca.sock, &err);
if (!nsock)
return err;
if (nsock->sk->sk_state != BT_CONNECTED) {
fput(nsock->file);
return -EBADFD;
}
err = bnep_add_connection(&ca, nsock);
if (!err) {
if (copy_to_user(argp, &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, argp, sizeof(cd)))
return -EFAULT;
return bnep_del_connection(&cd);
case BNEPGETCONNLIST:
if (copy_from_user(&cl, argp, sizeof(cl)))
return -EFAULT;
if (cl.cnum <= 0)
return -EINVAL;
err = bnep_get_connlist(&cl);
if (!err && copy_to_user(argp, &cl, sizeof(cl)))
return -EFAULT;
return err;
case BNEPGETCONNINFO:
if (copy_from_user(&ci, argp, sizeof(ci)))
return -EFAULT;
err = bnep_get_conninfo(&ci);
if (!err && copy_to_user(argp, &ci, sizeof(ci)))
return -EFAULT;
return err;
default:
return -EINVAL;
}
return 0;
}
#ifdef CONFIG_COMPAT
static int bnep_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
if (cmd == BNEPGETCONNLIST) {
struct bnep_connlist_req cl;
uint32_t uci;
int err;
if (get_user(cl.cnum, (uint32_t __user *) arg) ||
get_user(uci, (u32 __user *) (arg + 4)))
return -EFAULT;
cl.ci = compat_ptr(uci);
if (cl.cnum <= 0)
return -EINVAL;
err = bnep_get_connlist(&cl);
if (!err && put_user(cl.cnum, (uint32_t __user *) arg))
err = -EFAULT;
return err;
}
return bnep_sock_ioctl(sock, cmd, arg);
}
#endif
static const struct proto_ops bnep_sock_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.release = bnep_sock_release,
.ioctl = bnep_sock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = bnep_sock_compat_ioctl,
#endif
.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 struct proto bnep_proto = {
.name = "BNEP",
.owner = THIS_MODULE,
.obj_size = sizeof(struct bt_sock)
};
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;
sk = sk_alloc(PF_BLUETOOTH, GFP_ATOMIC, &bnep_proto, 1);
if (!sk)
return -ENOMEM;
sock_init_data(sock, sk);
sock->ops = &bnep_sock_ops;
sock->state = SS_UNCONNECTED;
sock_reset_flag(sk, SOCK_ZAPPED);
sk->sk_protocol = protocol;
sk->sk_state = BT_OPEN;
return 0;
}
static struct net_proto_family bnep_sock_family_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.create = bnep_sock_create
};
int __init bnep_sock_init(void)
{
int err;
err = proto_register(&bnep_proto, 0);
if (err < 0)
return err;
err = bt_sock_register(BTPROTO_BNEP, &bnep_sock_family_ops);
if (err < 0)
goto error;
return 0;
error:
BT_ERR("Can't register BNEP socket");
proto_unregister(&bnep_proto);
return err;
}
int __exit bnep_sock_cleanup(void)
{
if (bt_sock_unregister(BTPROTO_BNEP) < 0)
BT_ERR("Can't unregister BNEP socket");
proto_unregister(&bnep_proto);
return 0;
}

View File

@@ -0,0 +1,11 @@
config BT_CMTP
tristate "CMTP protocol support"
depends on BT && BT_L2CAP && ISDN_CAPI
help
CMTP (CAPI Message Transport Protocol) is a transport layer
for CAPI messages. CMTP is required for the Bluetooth Common
ISDN Access Profile.
Say Y here to compile CMTP support into the kernel or say M to
compile it as module (cmtp).

View File

@@ -0,0 +1,7 @@
#
# Makefile for the Linux Bluetooth CMTP layer
#
obj-$(CONFIG_BT_CMTP) += cmtp.o
cmtp-objs := core.o sock.o capi.o

623
net/bluetooth/cmtp/capi.c Normal file
View File

@@ -0,0 +1,623 @@
/*
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/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/skbuff.h>
#include <linux/socket.h>
#include <linux/ioctl.h>
#include <linux/file.h>
#include <linux/wait.h>
#include <net/sock.h>
#include <linux/isdn/capilli.h>
#include <linux/isdn/capicmd.h>
#include <linux/isdn/capiutil.h>
#include "cmtp.h"
#ifndef CONFIG_BT_CMTP_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
#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
#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 = kzalloc(sizeof(*app), GFP_KERNEL);
BT_DBG("session %p application %p appl %d", session, app, appl);
if (!app)
return NULL;
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_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 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:
if (skb->len < CAPI_MSG_BASELEN + 10)
break;
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:
if (skb->len < CAPI_MSG_BASELEN + 11 + sizeof(capi_profile))
break;
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;
capi_ctr_ready(ctrl);
}
break;
case CAPI_FUNCTION_GET_MANUFACTURER:
if (skb->len < CAPI_MSG_BASELEN + 15)
break;
controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 10);
if (!info && ctrl) {
int len = min_t(uint, CAPI_MANUFACTURER_LEN,
skb->data[CAPI_MSG_BASELEN + 14]);
memset(ctrl->manu, 0, CAPI_MANUFACTURER_LEN);
strncpy(ctrl->manu,
skb->data + CAPI_MSG_BASELEN + 15, len);
}
break;
case CAPI_FUNCTION_GET_VERSION:
if (skb->len < CAPI_MSG_BASELEN + 32)
break;
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:
if (skb->len < CAPI_MSG_BASELEN + 17)
break;
controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 12);
if (!info && ctrl) {
int len = min_t(uint, CAPI_SERIAL_LEN,
skb->data[CAPI_MSG_BASELEN + 16]);
memset(ctrl->serial, 0, CAPI_SERIAL_LEN);
strncpy(ctrl->serial,
skb->data + CAPI_MSG_BASELEN + 17, len);
}
break;
}
break;
case CAPI_IND:
if (skb->len < CAPI_MSG_BASELEN + 6)
break;
func = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 3);
if (func == CAPI_FUNCTION_LOOPBACK) {
int len = min_t(uint, skb->len - CAPI_MSG_BASELEN - 6,
skb->data[CAPI_MSG_BASELEN + 5]);
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, len);
}
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;
__u32 contr;
BT_DBG("session %p skb %p len %d", session, skb, skb->len);
if (skb->len < CAPI_MSG_BASELEN)
return;
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;
}
capi_ctr_handle_message(ctrl, appl, skb);
}
static int cmtp_load_firmware(struct capi_ctr *ctrl, capiloaddata *data)
{
BT_DBG("ctrl %p data %p", ctrl, data);
return 0;
}
static void cmtp_reset_ctr(struct capi_ctr *ctrl)
{
struct cmtp_session *session = ctrl->driverdata;
BT_DBG("ctrl %p", ctrl);
capi_ctr_reseted(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");
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) {
cmtp_application_del(session, application);
return;
}
}
static void cmtp_release_appl(struct capi_ctr *ctrl, __u16 appl)
{
struct cmtp_session *session = ctrl->driverdata;
struct cmtp_application *application;
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);
wait_event_interruptible_timeout(session->wait,
(application->state == BT_CLOSED), CMTP_INTEROP_TIMEOUT);
cmtp_application_del(session, application);
}
static u16 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);
return CAPI_ILLAPPNR;
}
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);
return CAPI_NOERROR;
}
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\n\n", cmtp_procinfo(ctrl));
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);
}
int cmtp_attach_device(struct cmtp_session *session)
{
unsigned char buf[4];
long ret;
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);
ret = wait_event_interruptible_timeout(session->wait,
session->ncontroller, CMTP_INTEROP_TIMEOUT);
BT_INFO("Found %d CAPI controller(s) on device %s", session->ncontroller, session->name);
if (!ret)
return -ETIMEDOUT;
if (!session->ncontroller)
return -ENODEV;
if (session->ncontroller > 1)
BT_INFO("Setting up only CAPI controller 1");
session->ctrl.owner = THIS_MODULE;
session->ctrl.driverdata = session;
strcpy(session->ctrl.name, session->name);
session->ctrl.driver_name = "cmtp";
session->ctrl.load_firmware = cmtp_load_firmware;
session->ctrl.reset_ctr = cmtp_reset_ctr;
session->ctrl.register_appl = cmtp_register_appl;
session->ctrl.release_appl = cmtp_release_appl;
session->ctrl.send_message = cmtp_send_message;
session->ctrl.procinfo = cmtp_procinfo;
session->ctrl.ctr_read_proc = cmtp_ctr_read_proc;
if (attach_capi_ctr(&session->ctrl) < 0) {
BT_ERR("Can't attach new controller");
return -EBUSY;
}
session->num = session->ctrl.cnr;
BT_DBG("session %p num %d", session, 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)
{
BT_DBG("session %p", session);
detach_capi_ctr(&session->ctrl);
}

135
net/bluetooth/cmtp/cmtp.h Normal file
View File

@@ -0,0 +1,135 @@
/*
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 __user *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);
static inline void cmtp_schedule(struct cmtp_session *session)
{
struct sock *sk = session->sock->sk;
wake_up_interruptible(sk->sk_sleep);
}
/* CMTP init defines */
int cmtp_init_sockets(void);
void cmtp_cleanup_sockets(void);
#endif /* __CMTP_H */

499
net/bluetooth/cmtp/core.c Normal file
View File

@@ -0,0 +1,499 @@
/*
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/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/skbuff.h>
#include <linux/socket.h>
#include <linux/ioctl.h>
#include <linux/file.h>
#include <linux/init.h>
#include <net/sock.h>
#include <linux/isdn/capilli.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/l2cap.h>
#include "cmtp.h"
#ifndef CONFIG_BT_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)
{
__module_get(THIS_MODULE);
list_add(&session->list, &cmtp_session_list);
}
static void __cmtp_unlink_session(struct cmtp_session *session)
{
list_del(&session->list);
module_put(THIS_MODULE);
}
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 kvec iv = { data, len };
struct msghdr msg;
BT_DBG("session %p data %p len %d", session, data, len);
if (!len)
return 0;
memset(&msg, 0, sizeof(msg));
return kernel_sendmsg(sock, &msg, &iv, 1, len);
}
static void 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;
}
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);
}
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("kcmtpd_ctr_%d", session->num);
set_user_nice(current, -15);
current->flags |= PF_NOFREEZE;
init_waitqueue_entry(&wait, current);
add_wait_queue(sk->sk_sleep, &wait);
while (!atomic_read(&session->terminate)) {
set_current_state(TASK_INTERRUPTIBLE);
if (sk->sk_state != BT_CONNECTED)
break;
while ((skb = skb_dequeue(&sk->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->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, &bt_sk(sock->sk)->src);
baswap(&dst, &bt_sk(sock->sk)->dst);
session = kzalloc(sizeof(struct cmtp_session), GFP_KERNEL);
if (!session)
return -ENOMEM;
down_write(&cmtp_session_sem);
s = __cmtp_get_session(&bt_sk(sock->sk)->dst);
if (s && s->state == BT_CONNECTED) {
err = -EEXIST;
goto failed;
}
bacpy(&session->bdaddr, &bt_sk(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->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_KERNEL);
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;
}
static int __init cmtp_init(void)
{
l2cap_load();
BT_INFO("CMTP (CAPI Emulation) ver %s", VERSION);
cmtp_init_sockets();
return 0;
}
static void __exit cmtp_exit(void)
{
cmtp_cleanup_sockets();
}
module_init(cmtp_init);
module_exit(cmtp_exit);
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Bluetooth CMTP ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");
MODULE_ALIAS("bt-proto-5");

257
net/bluetooth/cmtp/sock.c Normal file
View File

@@ -0,0 +1,257 @@
/*
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/module.h>
#include <linux/types.h>
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/kernel.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/compat.h>
#include <net/sock.h>
#include <linux/isdn/capilli.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include "cmtp.h"
#ifndef CONFIG_BT_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);
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;
void __user *argp = (void __user *)arg;
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, argp, sizeof(ca)))
return -EFAULT;
nsock = sockfd_lookup(ca.sock, &err);
if (!nsock)
return err;
if (nsock->sk->sk_state != BT_CONNECTED) {
fput(nsock->file);
return -EBADFD;
}
err = cmtp_add_connection(&ca, nsock);
if (!err) {
if (copy_to_user(argp, &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, argp, sizeof(cd)))
return -EFAULT;
return cmtp_del_connection(&cd);
case CMTPGETCONNLIST:
if (copy_from_user(&cl, argp, sizeof(cl)))
return -EFAULT;
if (cl.cnum <= 0)
return -EINVAL;
err = cmtp_get_connlist(&cl);
if (!err && copy_to_user(argp, &cl, sizeof(cl)))
return -EFAULT;
return err;
case CMTPGETCONNINFO:
if (copy_from_user(&ci, argp, sizeof(ci)))
return -EFAULT;
err = cmtp_get_conninfo(&ci);
if (!err && copy_to_user(argp, &ci, sizeof(ci)))
return -EFAULT;
return err;
}
return -EINVAL;
}
#ifdef CONFIG_COMPAT
static int cmtp_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
if (cmd == CMTPGETCONNLIST) {
struct cmtp_connlist_req cl;
uint32_t uci;
int err;
if (get_user(cl.cnum, (uint32_t __user *) arg) ||
get_user(uci, (u32 __user *) (arg + 4)))
return -EFAULT;
cl.ci = compat_ptr(uci);
if (cl.cnum <= 0)
return -EINVAL;
err = cmtp_get_connlist(&cl);
if (!err && put_user(cl.cnum, (uint32_t __user *) arg))
err = -EFAULT;
return err;
}
return cmtp_sock_ioctl(sock, cmd, arg);
}
#endif
static const struct proto_ops cmtp_sock_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.release = cmtp_sock_release,
.ioctl = cmtp_sock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = cmtp_sock_compat_ioctl,
#endif
.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 struct proto cmtp_proto = {
.name = "CMTP",
.owner = THIS_MODULE,
.obj_size = sizeof(struct bt_sock)
};
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;
sk = sk_alloc(PF_BLUETOOTH, GFP_ATOMIC, &cmtp_proto, 1);
if (!sk)
return -ENOMEM;
sock_init_data(sock, sk);
sock->ops = &cmtp_sock_ops;
sock->state = SS_UNCONNECTED;
sock_reset_flag(sk, SOCK_ZAPPED);
sk->sk_protocol = protocol;
sk->sk_state = BT_OPEN;
return 0;
}
static struct net_proto_family cmtp_sock_family_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.create = cmtp_sock_create
};
int cmtp_init_sockets(void)
{
int err;
err = proto_register(&cmtp_proto, 0);
if (err < 0)
return err;
err = bt_sock_register(BTPROTO_CMTP, &cmtp_sock_family_ops);
if (err < 0)
goto error;
return 0;
error:
BT_ERR("Can't register CMTP socket");
proto_unregister(&cmtp_proto);
return err;
}
void cmtp_cleanup_sockets(void)
{
if (bt_sock_unregister(BTPROTO_CMTP) < 0)
BT_ERR("Can't unregister CMTP socket");
proto_unregister(&cmtp_proto);
}

577
net/bluetooth/hci_conn.c Normal file
View File

@@ -0,0 +1,577 @@
/*
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.
*/
/* Bluetooth HCI connection handling. */
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.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 CONFIG_BT_HCI_CORE_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
void hci_acl_connect(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
struct inquiry_entry *ie;
struct hci_cp_create_conn cp;
BT_DBG("%p", conn);
conn->state = BT_CONNECT;
conn->out = 1;
conn->link_mode = HCI_LM_MASTER;
conn->attempt++;
memset(&cp, 0, sizeof(cp));
bacpy(&cp.bdaddr, &conn->dst);
cp.pscan_rep_mode = 0x02;
if ((ie = hci_inquiry_cache_lookup(hdev, &conn->dst)) &&
inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) {
cp.pscan_rep_mode = ie->data.pscan_rep_mode;
cp.pscan_mode = ie->data.pscan_mode;
cp.clock_offset = ie->data.clock_offset | __cpu_to_le16(0x8000);
memcpy(conn->dev_class, ie->data.dev_class, 3);
}
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, sizeof(cp), &cp);
}
static void hci_acl_connect_cancel(struct hci_conn *conn)
{
struct hci_cp_create_conn_cancel cp;
BT_DBG("%p", conn);
if (conn->hdev->hci_ver < 2)
return;
bacpy(&cp.bdaddr, &conn->dst);
hci_send_cmd(conn->hdev, OGF_LINK_CTL,
OCF_CREATE_CONN_CANCEL, sizeof(cp), &cp);
}
void hci_acl_disconn(struct hci_conn *conn, __u8 reason)
{
struct hci_cp_disconnect 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, sizeof(cp), &cp);
}
void hci_add_sco(struct hci_conn *conn, __u16 handle)
{
struct hci_dev *hdev = conn->hdev;
struct hci_cp_add_sco 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, sizeof(cp), &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);
switch (conn->state) {
case BT_CONNECT:
hci_acl_connect_cancel(conn);
break;
case BT_CONNECTED:
hci_acl_disconn(conn, 0x13);
break;
default:
conn->state = BT_CLOSED;
break;
}
hci_dev_unlock(hdev);
}
static void hci_conn_idle(unsigned long arg)
{
struct hci_conn *conn = (void *) arg;
BT_DBG("conn %p mode %d", conn, conn->mode);
hci_conn_enter_sniff_mode(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));
conn = kzalloc(sizeof(struct hci_conn), GFP_ATOMIC);
if (!conn)
return NULL;
bacpy(&conn->dst, dst);
conn->hdev = hdev;
conn->type = type;
conn->mode = HCI_CM_ACTIVE;
conn->state = BT_OPEN;
conn->power_save = 1;
skb_queue_head_init(&conn->data_q);
init_timer(&conn->disc_timer);
conn->disc_timer.function = hci_conn_timeout;
conn->disc_timer.data = (unsigned long) conn;
init_timer(&conn->idle_timer);
conn->idle_timer.function = hci_conn_idle;
conn->idle_timer.data = (unsigned long) conn;
atomic_set(&conn->refcnt, 0);
hci_dev_hold(hdev);
tasklet_disable(&hdev->tx_task);
hci_conn_hash_add(hdev, conn);
if (hdev->notify)
hdev->notify(hdev, HCI_NOTIFY_CONN_ADD);
hci_conn_add_sysfs(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);
del_timer(&conn->idle_timer);
del_timer(&conn->disc_timer);
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);
hci_conn_del_sysfs(conn);
hci_conn_hash_del(hdev, conn);
if (hdev->notify)
hdev->notify(hdev, HCI_NOTIFY_CONN_DEL);
tasklet_enable(&hdev->tx_task);
skb_queue_purge(&conn->data_q);
hci_dev_put(hdev);
/* will free via device release */
put_device(&conn->dev);
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(&hci_dev_list_lock);
list_for_each(p, &hci_dev_list) {
struct hci_dev *d = list_entry(p, struct hci_dev, list);
if (!test_bit(HCI_UP, &d->flags) || test_bit(HCI_RAW, &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)
hdev = hci_dev_hold(hdev);
read_unlock_bh(&hci_dev_list_lock);
return hdev;
}
EXPORT_SYMBOL(hci_get_route);
/* 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 = hci_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 = hci_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;
}
}
EXPORT_SYMBOL(hci_connect);
/* 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)) {
struct hci_cp_auth_requested cp;
cp.handle = __cpu_to_le16(conn->handle);
hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_AUTH_REQUESTED, sizeof(cp), &cp);
}
return 0;
}
EXPORT_SYMBOL(hci_conn_auth);
/* 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)) {
struct hci_cp_set_conn_encrypt cp;
cp.handle = __cpu_to_le16(conn->handle);
cp.encrypt = 1;
hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT, sizeof(cp), &cp);
}
return 0;
}
EXPORT_SYMBOL(hci_conn_encrypt);
/* Change link key */
int hci_conn_change_link_key(struct hci_conn *conn)
{
BT_DBG("conn %p", conn);
if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) {
struct hci_cp_change_conn_link_key cp;
cp.handle = __cpu_to_le16(conn->handle);
hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_CHANGE_CONN_LINK_KEY, sizeof(cp), &cp);
}
return 0;
}
EXPORT_SYMBOL(hci_conn_change_link_key);
/* Switch role */
int hci_conn_switch_role(struct hci_conn *conn, uint8_t role)
{
BT_DBG("conn %p", conn);
if (!role && conn->link_mode & HCI_LM_MASTER)
return 1;
if (!test_and_set_bit(HCI_CONN_RSWITCH_PEND, &conn->pend)) {
struct hci_cp_switch_role cp;
bacpy(&cp.bdaddr, &conn->dst);
cp.role = role;
hci_send_cmd(conn->hdev, OGF_LINK_POLICY, OCF_SWITCH_ROLE, sizeof(cp), &cp);
}
return 0;
}
EXPORT_SYMBOL(hci_conn_switch_role);
/* Enter active mode */
void hci_conn_enter_active_mode(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
BT_DBG("conn %p mode %d", conn, conn->mode);
if (test_bit(HCI_RAW, &hdev->flags))
return;
if (conn->mode != HCI_CM_SNIFF || !conn->power_save)
goto timer;
if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) {
struct hci_cp_exit_sniff_mode cp;
cp.handle = __cpu_to_le16(conn->handle);
hci_send_cmd(hdev, OGF_LINK_POLICY,
OCF_EXIT_SNIFF_MODE, sizeof(cp), &cp);
}
timer:
if (hdev->idle_timeout > 0)
mod_timer(&conn->idle_timer,
jiffies + msecs_to_jiffies(hdev->idle_timeout));
}
/* Enter sniff mode */
void hci_conn_enter_sniff_mode(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
BT_DBG("conn %p mode %d", conn, conn->mode);
if (test_bit(HCI_RAW, &hdev->flags))
return;
if (!lmp_sniff_capable(hdev) || !lmp_sniff_capable(conn))
return;
if (conn->mode != HCI_CM_ACTIVE || !(conn->link_policy & HCI_LP_SNIFF))
return;
if (lmp_sniffsubr_capable(hdev) && lmp_sniffsubr_capable(conn)) {
struct hci_cp_sniff_subrate cp;
cp.handle = __cpu_to_le16(conn->handle);
cp.max_latency = __constant_cpu_to_le16(0);
cp.min_remote_timeout = __constant_cpu_to_le16(0);
cp.min_local_timeout = __constant_cpu_to_le16(0);
hci_send_cmd(hdev, OGF_LINK_POLICY,
OCF_SNIFF_SUBRATE, sizeof(cp), &cp);
}
if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) {
struct hci_cp_sniff_mode cp;
cp.handle = __cpu_to_le16(conn->handle);
cp.max_interval = __cpu_to_le16(hdev->sniff_max_interval);
cp.min_interval = __cpu_to_le16(hdev->sniff_min_interval);
cp.attempt = __constant_cpu_to_le16(4);
cp.timeout = __constant_cpu_to_le16(1);
hci_send_cmd(hdev, OGF_LINK_POLICY,
OCF_SNIFF_MODE, sizeof(cp), &cp);
}
}
/* Drop all connection on the device */
void hci_conn_hash_flush(struct hci_dev *hdev)
{
struct hci_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(void __user *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, 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 = 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(arg, cl, size);
kfree(cl);
return err ? -EFAULT : 0;
}
int hci_get_conn_info(struct hci_dev *hdev, void __user *arg)
{
struct hci_conn_info_req req;
struct hci_conn_info ci;
struct hci_conn *conn;
char __user *ptr = arg + sizeof(req);
if (copy_from_user(&req, arg, sizeof(req)))
return -EFAULT;
hci_dev_lock_bh(hdev);
conn = hci_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;
}

1444
net/bluetooth/hci_core.c Normal file

File diff suppressed because it is too large Load Diff

1325
net/bluetooth/hci_event.c Normal file

File diff suppressed because it is too large Load Diff

722
net/bluetooth/hci_sock.c Normal file
View File

@@ -0,0 +1,722 @@
/*
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.
*/
/* Bluetooth HCI sockets. */
#include <linux/module.h>
#include <linux/types.h>
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/workqueue.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 CONFIG_BT_HCI_SOCK_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
/* ----- HCI socket interface ----- */
static inline int hci_test_bit(int nr, void *addr)
{
return *((__u32 *) addr + (nr >> 5)) & ((__u32) 1 << (nr & 31));
}
/* Security filter */
static struct hci_sec_filter hci_sec_filter = {
/* Packet types */
0x10,
/* Events */
{ 0x1000d9fe, 0x0000b00c },
/* Commands */
{
{ 0x0 },
/* OGF_LINK_CTL */
{ 0xbe000006, 0x00000001, 0x000000, 0x00 },
/* OGF_LINK_POLICY */
{ 0x00005200, 0x00000000, 0x000000, 0x00 },
/* OGF_HOST_CTL */
{ 0xaab00200, 0x2b402aaa, 0x020154, 0x00 },
/* OGF_INFO_PARAM */
{ 0x000002be, 0x00000000, 0x000000, 0x00 },
/* OGF_STATUS_PARAM */
{ 0x000000ea, 0x00000000, 0x000000, 0x00 }
}
};
static struct bt_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;
struct hlist_node *node;
BT_DBG("hdev %p len %d", hdev, skb->len);
read_lock(&hci_sk_list.lock);
sk_for_each(sk, node, &hci_sk_list.head) {
struct hci_filter *flt;
struct sk_buff *nskb;
if (sk->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 (!test_bit((bt_cb(skb)->pkt_type == HCI_VENDOR_PKT) ?
0 : (bt_cb(skb)->pkt_type & HCI_FLT_TYPE_BITS), &flt->type_mask))
continue;
if (bt_cb(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 == HCI_EV_CMD_COMPLETE &&
flt->opcode !=
get_unaligned((__le16 *)(skb->data + 3))) ||
(evt == HCI_EV_CMD_STATUS &&
flt->opcode !=
get_unaligned((__le16 *)(skb->data + 4)))))
continue;
}
if (!(nskb = skb_clone(skb, GFP_ATOMIC)))
continue;
/* Put type byte before the data */
memcpy(skb_push(nskb, 1), &bt_cb(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;
BT_DBG("sock %p sk %p", sock, sk);
if (!sk)
return 0;
hdev = hci_pi(sk)->hdev;
bt_sock_unlink(&hci_sk_list, sk);
if (hdev) {
atomic_dec(&hdev->promisc);
hci_dev_put(hdev);
}
sock_orphan(sk);
skb_queue_purge(&sk->sk_receive_queue);
skb_queue_purge(&sk->sk_write_queue);
sock_put(sk);
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 (test_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks))
return -EPERM;
if (arg)
set_bit(HCI_RAW, &hdev->flags);
else
clear_bit(HCI_RAW, &hdev->flags);
return 0;
case HCISETSECMGR:
if (!capable(CAP_NET_ADMIN))
return -EACCES;
if (arg)
set_bit(HCI_SECMGR, &hdev->flags);
else
clear_bit(HCI_SECMGR, &hdev->flags);
return 0;
case HCIGETCONNINFO:
return hci_get_conn_info(hdev, (void __user *)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;
void __user *argp = (void __user *)arg;
int err;
BT_DBG("cmd %x arg %lx", cmd, arg);
switch (cmd) {
case HCIGETDEVLIST:
return hci_get_dev_list(argp);
case HCIGETDEVINFO:
return hci_get_dev_info(argp);
case HCIGETCONNLIST:
return hci_get_conn_list(argp);
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, argp);
case HCIINQUIRY:
return hci_inquiry(argp);
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->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;
struct hci_dev *hdev = hci_pi(sk)->hdev;
BT_DBG("sock %p sk %p", sock, sk);
if (!hdev)
return -EBADFD;
lock_sock(sk);
*addr_len = sizeof(*haddr);
haddr->hci_family = AF_BLUETOOTH;
haddr->hci_dev = 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) {
int incoming = bt_cb(skb)->incoming;
put_cmsg(msg, SOL_HCI, HCI_CMSG_DIR, sizeof(incoming), &incoming);
}
if (mask & HCI_CMSG_TSTAMP) {
struct timeval tv;
skb_get_timestamp(skb, &tv);
put_cmsg(msg, SOL_HCI, HCI_CMSG_TSTAMP, sizeof(tv), &tv);
}
}
static int hci_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t len, int flags)
{
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->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 kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t len)
{
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 || len > HCI_MAX_FRAME_SIZE)
return -EINVAL;
lock_sock(sk);
if (!(hdev = hci_pi(sk)->hdev)) {
err = -EBADFD;
goto done;
}
if (!(skb = bt_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;
}
bt_cb(skb)->pkt_type = *((unsigned char *) skb->data);
skb_pull(skb, 1);
skb->dev = (void *) hdev;
if (bt_cb(skb)->pkt_type == HCI_COMMAND_PKT) {
u16 opcode = __le16_to_cpu(get_unaligned((__le16 *) skb->data));
u16 ogf = hci_opcode_ogf(opcode);
u16 ocf = hci_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;
}
static int hci_sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, int len)
{
struct hci_ufilter uf = { .opcode = 0 };
struct sock *sk = sock->sk;
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 __user *)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 __user *)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_t(unsigned int, len, sizeof(uf));
if (copy_from_user(&uf, optval, len)) {
err = -EFAULT;
break;
}
if (!capable(CAP_NET_RAW)) {
uf.type_mask &= hci_sec_filter.type_mask;
uf.event_mask[0] &= *((u32 *) hci_sec_filter.event_mask + 0);
uf.event_mask[1] &= *((u32 *) hci_sec_filter.event_mask + 1);
}
{
struct hci_filter *f = &hci_pi(sk)->filter;
f->type_mask = uf.type_mask;
f->opcode = uf.opcode;
*((u32 *) f->event_mask + 0) = uf.event_mask[0];
*((u32 *) f->event_mask + 1) = uf.event_mask[1];
}
break;
default:
err = -ENOPROTOOPT;
break;
}
release_sock(sk);
return err;
}
static int hci_sock_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
{
struct hci_ufilter uf;
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:
{
struct hci_filter *f = &hci_pi(sk)->filter;
uf.type_mask = f->type_mask;
uf.opcode = f->opcode;
uf.event_mask[0] = *((u32 *) f->event_mask + 0);
uf.event_mask[1] = *((u32 *) f->event_mask + 1);
}
len = min_t(unsigned int, len, sizeof(uf));
if (copy_to_user(optval, &uf, len))
return -EFAULT;
break;
default:
return -ENOPROTOOPT;
break;
}
return 0;
}
static const struct proto_ops hci_sock_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.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 struct proto hci_sk_proto = {
.name = "HCI",
.owner = THIS_MODULE,
.obj_size = sizeof(struct hci_pinfo)
};
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;
sk = sk_alloc(PF_BLUETOOTH, GFP_ATOMIC, &hci_sk_proto, 1);
if (!sk)
return -ENOMEM;
sock_init_data(sock, sk);
sock_reset_flag(sk, SOCK_ZAPPED);
sk->sk_protocol = protocol;
sock->state = SS_UNCONNECTED;
sk->sk_state = BT_OPEN;
bt_sock_link(&hci_sk_list, sk);
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;
struct hci_ev_si_device ev;
BT_DBG("hdev %s event %ld", hdev->name, event);
/* Send event to sockets */
ev.event = event;
ev.dev_id = hdev->id;
hci_si_event(NULL, HCI_EV_SI_DEVICE, sizeof(ev), &ev);
if (event == HCI_DEV_UNREG) {
struct sock *sk;
struct hlist_node *node;
/* Detach sockets from device */
read_lock(&hci_sk_list.lock);
sk_for_each(sk, node, &hci_sk_list.head) {
local_bh_disable();
bh_lock_sock_nested(sk);
if (hci_pi(sk)->hdev == hdev) {
hci_pi(sk)->hdev = NULL;
sk->sk_err = EPIPE;
sk->sk_state = BT_OPEN;
sk->sk_state_change(sk);
hci_dev_put(hdev);
}
bh_unlock_sock(sk);
local_bh_enable();
}
read_unlock(&hci_sk_list.lock);
}
return NOTIFY_DONE;
}
static struct net_proto_family hci_sock_family_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.create = hci_sock_create,
};
static struct notifier_block hci_sock_nblock = {
.notifier_call = hci_sock_dev_event
};
int __init hci_sock_init(void)
{
int err;
err = proto_register(&hci_sk_proto, 0);
if (err < 0)
return err;
err = bt_sock_register(BTPROTO_HCI, &hci_sock_family_ops);
if (err < 0)
goto error;
hci_register_notifier(&hci_sock_nblock);
BT_INFO("HCI socket layer initialized");
return 0;
error:
BT_ERR("HCI socket registration failed");
proto_unregister(&hci_sk_proto);
return err;
}
int __exit hci_sock_cleanup(void)
{
if (bt_sock_unregister(BTPROTO_HCI) < 0)
BT_ERR("HCI socket unregistration failed");
hci_unregister_notifier(&hci_sock_nblock);
proto_unregister(&hci_sk_proto);
return 0;
}

366
net/bluetooth/hci_sysfs.c Normal file
View File

@@ -0,0 +1,366 @@
/* Bluetooth HCI driver model support. */
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#ifndef CONFIG_BT_HCI_CORE_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
static inline char *typetostr(int type)
{
switch (type) {
case HCI_VIRTUAL:
return "VIRTUAL";
case HCI_USB:
return "USB";
case HCI_PCCARD:
return "PCCARD";
case HCI_UART:
return "UART";
case HCI_RS232:
return "RS232";
case HCI_PCI:
return "PCI";
case HCI_SDIO:
return "SDIO";
default:
return "UNKNOWN";
}
}
static ssize_t show_type(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", typetostr(hdev->type));
}
static ssize_t show_address(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
bdaddr_t bdaddr;
baswap(&bdaddr, &hdev->bdaddr);
return sprintf(buf, "%s\n", batostr(&bdaddr));
}
static ssize_t show_manufacturer(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", hdev->manufacturer);
}
static ssize_t show_hci_version(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", hdev->hci_ver);
}
static ssize_t show_hci_revision(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", hdev->hci_rev);
}
static ssize_t show_inquiry_cache(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
struct inquiry_cache *cache = &hdev->inq_cache;
struct inquiry_entry *e;
int n = 0;
hci_dev_lock_bh(hdev);
for (e = cache->list; e; e = e->next) {
struct inquiry_data *data = &e->data;
bdaddr_t bdaddr;
baswap(&bdaddr, &data->bdaddr);
n += sprintf(buf + n, "%s %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %u\n",
batostr(&bdaddr),
data->pscan_rep_mode, data->pscan_period_mode, data->pscan_mode,
data->dev_class[2], data->dev_class[1], data->dev_class[0],
__le16_to_cpu(data->clock_offset), data->rssi, e->timestamp);
}
hci_dev_unlock_bh(hdev);
return n;
}
static ssize_t show_idle_timeout(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", hdev->idle_timeout);
}
static ssize_t store_idle_timeout(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
char *ptr;
__u32 val;
val = simple_strtoul(buf, &ptr, 10);
if (ptr == buf)
return -EINVAL;
if (val != 0 && (val < 500 || val > 3600000))
return -EINVAL;
hdev->idle_timeout = val;
return count;
}
static ssize_t show_sniff_max_interval(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", hdev->sniff_max_interval);
}
static ssize_t store_sniff_max_interval(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
char *ptr;
__u16 val;
val = simple_strtoul(buf, &ptr, 10);
if (ptr == buf)
return -EINVAL;
if (val < 0x0002 || val > 0xFFFE || val % 2)
return -EINVAL;
if (val < hdev->sniff_min_interval)
return -EINVAL;
hdev->sniff_max_interval = val;
return count;
}
static ssize_t show_sniff_min_interval(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", hdev->sniff_min_interval);
}
static ssize_t store_sniff_min_interval(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
struct hci_dev *hdev = dev_get_drvdata(dev);
char *ptr;
__u16 val;
val = simple_strtoul(buf, &ptr, 10);
if (ptr == buf)
return -EINVAL;
if (val < 0x0002 || val > 0xFFFE || val % 2)
return -EINVAL;
if (val > hdev->sniff_max_interval)
return -EINVAL;
hdev->sniff_min_interval = val;
return count;
}
static DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
static DEVICE_ATTR(address, S_IRUGO, show_address, NULL);
static DEVICE_ATTR(manufacturer, S_IRUGO, show_manufacturer, NULL);
static DEVICE_ATTR(hci_version, S_IRUGO, show_hci_version, NULL);
static DEVICE_ATTR(hci_revision, S_IRUGO, show_hci_revision, NULL);
static DEVICE_ATTR(inquiry_cache, S_IRUGO, show_inquiry_cache, NULL);
static DEVICE_ATTR(idle_timeout, S_IRUGO | S_IWUSR,
show_idle_timeout, store_idle_timeout);
static DEVICE_ATTR(sniff_max_interval, S_IRUGO | S_IWUSR,
show_sniff_max_interval, store_sniff_max_interval);
static DEVICE_ATTR(sniff_min_interval, S_IRUGO | S_IWUSR,
show_sniff_min_interval, store_sniff_min_interval);
static struct device_attribute *bt_attrs[] = {
&dev_attr_type,
&dev_attr_address,
&dev_attr_manufacturer,
&dev_attr_hci_version,
&dev_attr_hci_revision,
&dev_attr_inquiry_cache,
&dev_attr_idle_timeout,
&dev_attr_sniff_max_interval,
&dev_attr_sniff_min_interval,
NULL
};
static ssize_t show_conn_type(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_conn *conn = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", conn->type == ACL_LINK ? "ACL" : "SCO");
}
static ssize_t show_conn_address(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_conn *conn = dev_get_drvdata(dev);
bdaddr_t bdaddr;
baswap(&bdaddr, &conn->dst);
return sprintf(buf, "%s\n", batostr(&bdaddr));
}
#define CONN_ATTR(_name,_mode,_show,_store) \
struct device_attribute conn_attr_##_name = __ATTR(_name,_mode,_show,_store)
static CONN_ATTR(type, S_IRUGO, show_conn_type, NULL);
static CONN_ATTR(address, S_IRUGO, show_conn_address, NULL);
static struct device_attribute *conn_attrs[] = {
&conn_attr_type,
&conn_attr_address,
NULL
};
struct class *bt_class = NULL;
EXPORT_SYMBOL_GPL(bt_class);
static struct bus_type bt_bus = {
.name = "bluetooth",
};
static struct platform_device *bt_platform;
static void bt_release(struct device *dev)
{
void *data = dev_get_drvdata(dev);
kfree(data);
}
static void add_conn(struct work_struct *work)
{
struct hci_conn *conn = container_of(work, struct hci_conn, work);
int i;
if (device_add(&conn->dev) < 0) {
BT_ERR("Failed to register connection device");
return;
}
for (i = 0; conn_attrs[i]; i++)
if (device_create_file(&conn->dev, conn_attrs[i]) < 0)
BT_ERR("Failed to create connection attribute");
}
void hci_conn_add_sysfs(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
bdaddr_t *ba = &conn->dst;
BT_DBG("conn %p", conn);
conn->dev.bus = &bt_bus;
conn->dev.parent = &hdev->dev;
conn->dev.release = bt_release;
snprintf(conn->dev.bus_id, BUS_ID_SIZE,
"%s%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X",
conn->type == ACL_LINK ? "acl" : "sco",
ba->b[5], ba->b[4], ba->b[3],
ba->b[2], ba->b[1], ba->b[0]);
dev_set_drvdata(&conn->dev, conn);
device_initialize(&conn->dev);
INIT_WORK(&conn->work, add_conn);
schedule_work(&conn->work);
}
static void del_conn(struct work_struct *work)
{
struct hci_conn *conn = container_of(work, struct hci_conn, work);
device_del(&conn->dev);
}
void hci_conn_del_sysfs(struct hci_conn *conn)
{
BT_DBG("conn %p", conn);
if (!device_is_registered(&conn->dev))
return;
INIT_WORK(&conn->work, del_conn);
schedule_work(&conn->work);
}
int hci_register_sysfs(struct hci_dev *hdev)
{
struct device *dev = &hdev->dev;
unsigned int i;
int err;
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
dev->class = bt_class;
dev->parent = hdev->parent;
strlcpy(dev->bus_id, hdev->name, BUS_ID_SIZE);
dev->release = bt_release;
dev_set_drvdata(dev, hdev);
err = device_register(dev);
if (err < 0)
return err;
for (i = 0; bt_attrs[i]; i++)
if (device_create_file(dev, bt_attrs[i]) < 0)
BT_ERR("Failed to create device attribute");
return 0;
}
void hci_unregister_sysfs(struct hci_dev *hdev)
{
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
device_del(&hdev->dev);
}
int __init bt_sysfs_init(void)
{
int err;
bt_platform = platform_device_register_simple("bluetooth", -1, NULL, 0);
if (IS_ERR(bt_platform))
return PTR_ERR(bt_platform);
err = bus_register(&bt_bus);
if (err < 0) {
platform_device_unregister(bt_platform);
return err;
}
bt_class = class_create(THIS_MODULE, "bluetooth");
if (IS_ERR(bt_class)) {
bus_unregister(&bt_bus);
platform_device_unregister(bt_platform);
return PTR_ERR(bt_class);
}
return 0;
}
void bt_sysfs_cleanup(void)
{
class_destroy(bt_class);
bus_unregister(&bt_bus);
platform_device_unregister(bt_platform);
}

View File

@@ -0,0 +1,12 @@
config BT_HIDP
tristate "HIDP protocol support"
depends on BT && BT_L2CAP && INPUT
select HID
help
HIDP (Human Interface Device Protocol) is a transport layer
for HID reports. HIDP is required for the Bluetooth Human
Interface Device Profile.
Say Y here to compile HIDP support into the kernel or say M to
compile it as module (hidp).

View File

@@ -0,0 +1,7 @@
#
# Makefile for the Linux Bluetooth HIDP layer
#
obj-$(CONFIG_BT_HIDP) += hidp.o
hidp-objs := core.o sock.o

973
net/bluetooth/hidp/core.c Normal file
View File

@@ -0,0 +1,973 @@
/*
HIDP implementation for Linux Bluetooth stack (BlueZ).
Copyright (C) 2003-2004 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/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/skbuff.h>
#include <linux/socket.h>
#include <linux/ioctl.h>
#include <linux/file.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <net/sock.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include <net/bluetooth/l2cap.h>
#include "hidp.h"
#ifndef CONFIG_BT_HIDP_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
#define VERSION "1.2"
static DECLARE_RWSEM(hidp_session_sem);
static LIST_HEAD(hidp_session_list);
static unsigned char hidp_keycode[256] = {
0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3,
4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26,
27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0,
122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
150,158,159,128,136,177,178,176,142,152,173,140
};
static unsigned char hidp_mkeyspat[] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 };
static struct hidp_session *__hidp_get_session(bdaddr_t *bdaddr)
{
struct hidp_session *session;
struct list_head *p;
BT_DBG("");
list_for_each(p, &hidp_session_list) {
session = list_entry(p, struct hidp_session, list);
if (!bacmp(bdaddr, &session->bdaddr))
return session;
}
return NULL;
}
static void __hidp_link_session(struct hidp_session *session)
{
__module_get(THIS_MODULE);
list_add(&session->list, &hidp_session_list);
}
static void __hidp_unlink_session(struct hidp_session *session)
{
list_del(&session->list);
module_put(THIS_MODULE);
}
static void __hidp_copy_session(struct hidp_session *session, struct hidp_conninfo *ci)
{
bacpy(&ci->bdaddr, &session->bdaddr);
ci->flags = session->flags;
ci->state = session->state;
ci->vendor = 0x0000;
ci->product = 0x0000;
ci->version = 0x0000;
memset(ci->name, 0, 128);
if (session->input) {
ci->vendor = session->input->id.vendor;
ci->product = session->input->id.product;
ci->version = session->input->id.version;
if (session->input->name)
strncpy(ci->name, session->input->name, 128);
else
strncpy(ci->name, "HID Boot Device", 128);
}
if (session->hid) {
ci->vendor = session->hid->vendor;
ci->product = session->hid->product;
ci->version = session->hid->version;
strncpy(ci->name, session->hid->name, 128);
}
}
static inline int hidp_queue_event(struct hidp_session *session, struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned char newleds;
struct sk_buff *skb;
BT_DBG("session %p type %d code %d value %d", session, type, code, value);
if (type != EV_LED)
return -1;
newleds = (!!test_bit(LED_KANA, dev->led) << 3) |
(!!test_bit(LED_COMPOSE, dev->led) << 3) |
(!!test_bit(LED_SCROLLL, dev->led) << 2) |
(!!test_bit(LED_CAPSL, dev->led) << 1) |
(!!test_bit(LED_NUML, dev->led));
if (session->leds == newleds)
return 0;
session->leds = newleds;
if (!(skb = alloc_skb(3, GFP_ATOMIC))) {
BT_ERR("Can't allocate memory for new frame");
return -ENOMEM;
}
*skb_put(skb, 1) = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT;
*skb_put(skb, 1) = 0x01;
*skb_put(skb, 1) = newleds;
skb_queue_tail(&session->intr_transmit, skb);
hidp_schedule(session);
return 0;
}
static int hidp_hidinput_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct hid_device *hid = dev->private;
struct hidp_session *session = hid->driver_data;
return hidp_queue_event(session, dev, type, code, value);
}
static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct hidp_session *session = dev->private;
return hidp_queue_event(session, dev, type, code, value);
}
static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
{
struct input_dev *dev = session->input;
unsigned char *keys = session->keys;
unsigned char *udata = skb->data + 1;
signed char *sdata = skb->data + 1;
int i, size = skb->len - 1;
switch (skb->data[0]) {
case 0x01: /* Keyboard report */
for (i = 0; i < 8; i++)
input_report_key(dev, hidp_keycode[i + 224], (udata[0] >> i) & 1);
/* If all the key codes have been set to 0x01, it means
* too many keys were pressed at the same time. */
if (!memcmp(udata + 2, hidp_mkeyspat, 6))
break;
for (i = 2; i < 8; i++) {
if (keys[i] > 3 && memscan(udata + 2, keys[i], 6) == udata + 8) {
if (hidp_keycode[keys[i]])
input_report_key(dev, hidp_keycode[keys[i]], 0);
else
BT_ERR("Unknown key (scancode %#x) released.", keys[i]);
}
if (udata[i] > 3 && memscan(keys + 2, udata[i], 6) == keys + 8) {
if (hidp_keycode[udata[i]])
input_report_key(dev, hidp_keycode[udata[i]], 1);
else
BT_ERR("Unknown key (scancode %#x) pressed.", udata[i]);
}
}
memcpy(keys, udata, 8);
break;
case 0x02: /* Mouse report */
input_report_key(dev, BTN_LEFT, sdata[0] & 0x01);
input_report_key(dev, BTN_RIGHT, sdata[0] & 0x02);
input_report_key(dev, BTN_MIDDLE, sdata[0] & 0x04);
input_report_key(dev, BTN_SIDE, sdata[0] & 0x08);
input_report_key(dev, BTN_EXTRA, sdata[0] & 0x10);
input_report_rel(dev, REL_X, sdata[1]);
input_report_rel(dev, REL_Y, sdata[2]);
if (size > 3)
input_report_rel(dev, REL_WHEEL, sdata[3]);
break;
}
input_sync(dev);
}
static inline int hidp_queue_report(struct hidp_session *session, unsigned char *data, int size)
{
struct sk_buff *skb;
BT_DBG("session %p hid %p data %p size %d", session, device, data, size);
if (!(skb = alloc_skb(size + 1, GFP_ATOMIC))) {
BT_ERR("Can't allocate memory for new frame");
return -ENOMEM;
}
*skb_put(skb, 1) = 0xa2;
if (size > 0)
memcpy(skb_put(skb, size), data, size);
skb_queue_tail(&session->intr_transmit, skb);
hidp_schedule(session);
return 0;
}
static int hidp_send_report(struct hidp_session *session, struct hid_report *report)
{
unsigned char buf[32];
int rsize;
rsize = ((report->size - 1) >> 3) + 1 + (report->id > 0);
if (rsize > sizeof(buf))
return -EIO;
hid_output_report(report, buf);
return hidp_queue_report(session, buf, rsize);
}
static void hidp_idle_timeout(unsigned long arg)
{
struct hidp_session *session = (struct hidp_session *) arg;
atomic_inc(&session->terminate);
hidp_schedule(session);
}
static inline void hidp_set_timer(struct hidp_session *session)
{
if (session->idle_to > 0)
mod_timer(&session->timer, jiffies + HZ * session->idle_to);
}
static inline void hidp_del_timer(struct hidp_session *session)
{
if (session->idle_to > 0)
del_timer(&session->timer);
}
static int __hidp_send_ctrl_message(struct hidp_session *session,
unsigned char hdr, unsigned char *data, int size)
{
struct sk_buff *skb;
BT_DBG("session %p data %p size %d", session, data, size);
if (!(skb = alloc_skb(size + 1, GFP_ATOMIC))) {
BT_ERR("Can't allocate memory for new frame");
return -ENOMEM;
}
*skb_put(skb, 1) = hdr;
if (data && size > 0)
memcpy(skb_put(skb, size), data, size);
skb_queue_tail(&session->ctrl_transmit, skb);
return 0;
}
static inline int hidp_send_ctrl_message(struct hidp_session *session,
unsigned char hdr, unsigned char *data, int size)
{
int err;
err = __hidp_send_ctrl_message(session, hdr, data, size);
hidp_schedule(session);
return err;
}
static inline void hidp_process_handshake(struct hidp_session *session, unsigned char param)
{
BT_DBG("session %p param 0x%02x", session, param);
switch (param) {
case HIDP_HSHK_SUCCESSFUL:
/* FIXME: Call into SET_ GET_ handlers here */
break;
case HIDP_HSHK_NOT_READY:
case HIDP_HSHK_ERR_INVALID_REPORT_ID:
case HIDP_HSHK_ERR_UNSUPPORTED_REQUEST:
case HIDP_HSHK_ERR_INVALID_PARAMETER:
/* FIXME: Call into SET_ GET_ handlers here */
break;
case HIDP_HSHK_ERR_UNKNOWN:
break;
case HIDP_HSHK_ERR_FATAL:
/* Device requests a reboot, as this is the only way this error
* can be recovered. */
__hidp_send_ctrl_message(session,
HIDP_TRANS_HID_CONTROL | HIDP_CTRL_SOFT_RESET, NULL, 0);
break;
default:
__hidp_send_ctrl_message(session,
HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0);
break;
}
}
static inline void hidp_process_hid_control(struct hidp_session *session, unsigned char param)
{
BT_DBG("session %p param 0x%02x", session, param);
switch (param) {
case HIDP_CTRL_NOP:
break;
case HIDP_CTRL_VIRTUAL_CABLE_UNPLUG:
/* Flush the transmit queues */
skb_queue_purge(&session->ctrl_transmit);
skb_queue_purge(&session->intr_transmit);
/* Kill session thread */
atomic_inc(&session->terminate);
break;
case HIDP_CTRL_HARD_RESET:
case HIDP_CTRL_SOFT_RESET:
case HIDP_CTRL_SUSPEND:
case HIDP_CTRL_EXIT_SUSPEND:
/* FIXME: We have to parse these and return no error */
break;
default:
__hidp_send_ctrl_message(session,
HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0);
break;
}
}
static inline void hidp_process_data(struct hidp_session *session, struct sk_buff *skb, unsigned char param)
{
BT_DBG("session %p skb %p len %d param 0x%02x", session, skb, skb->len, param);
switch (param) {
case HIDP_DATA_RTYPE_INPUT:
hidp_set_timer(session);
if (session->input)
hidp_input_report(session, skb);
if (session->hid)
hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0);
break;
case HIDP_DATA_RTYPE_OTHER:
case HIDP_DATA_RTYPE_OUPUT:
case HIDP_DATA_RTYPE_FEATURE:
break;
default:
__hidp_send_ctrl_message(session,
HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0);
}
}
static inline void hidp_recv_ctrl_frame(struct hidp_session *session, struct sk_buff *skb)
{
unsigned char hdr, type, param;
BT_DBG("session %p skb %p len %d", session, skb, skb->len);
hdr = skb->data[0];
skb_pull(skb, 1);
type = hdr & HIDP_HEADER_TRANS_MASK;
param = hdr & HIDP_HEADER_PARAM_MASK;
switch (type) {
case HIDP_TRANS_HANDSHAKE:
hidp_process_handshake(session, param);
break;
case HIDP_TRANS_HID_CONTROL:
hidp_process_hid_control(session, param);
break;
case HIDP_TRANS_DATA:
hidp_process_data(session, skb, param);
break;
default:
__hidp_send_ctrl_message(session,
HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_UNSUPPORTED_REQUEST, NULL, 0);
break;
}
kfree_skb(skb);
}
static inline void hidp_recv_intr_frame(struct hidp_session *session, struct sk_buff *skb)
{
unsigned char hdr;
BT_DBG("session %p skb %p len %d", session, skb, skb->len);
hdr = skb->data[0];
skb_pull(skb, 1);
if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) {
hidp_set_timer(session);
if (session->input)
hidp_input_report(session, skb);
if (session->hid) {
hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 1);
BT_DBG("report len %d", skb->len);
}
} else {
BT_DBG("Unsupported protocol header 0x%02x", hdr);
}
kfree_skb(skb);
}
static int hidp_send_frame(struct socket *sock, unsigned char *data, int len)
{
struct kvec iv = { data, len };
struct msghdr msg;
BT_DBG("sock %p data %p len %d", sock, data, len);
if (!len)
return 0;
memset(&msg, 0, sizeof(msg));
return kernel_sendmsg(sock, &msg, &iv, 1, len);
}
static void hidp_process_transmit(struct hidp_session *session)
{
struct sk_buff *skb;
BT_DBG("session %p", session);
while ((skb = skb_dequeue(&session->ctrl_transmit))) {
if (hidp_send_frame(session->ctrl_sock, skb->data, skb->len) < 0) {
skb_queue_head(&session->ctrl_transmit, skb);
break;
}
hidp_set_timer(session);
kfree_skb(skb);
}
while ((skb = skb_dequeue(&session->intr_transmit))) {
if (hidp_send_frame(session->intr_sock, skb->data, skb->len) < 0) {
skb_queue_head(&session->intr_transmit, skb);
break;
}
hidp_set_timer(session);
kfree_skb(skb);
}
}
static int hidp_session(void *arg)
{
struct hidp_session *session = arg;
struct sock *ctrl_sk = session->ctrl_sock->sk;
struct sock *intr_sk = session->intr_sock->sk;
struct sk_buff *skb;
int vendor = 0x0000, product = 0x0000;
wait_queue_t ctrl_wait, intr_wait;
BT_DBG("session %p", session);
if (session->input) {
vendor = session->input->id.vendor;
product = session->input->id.product;
}
if (session->hid) {
vendor = session->hid->vendor;
product = session->hid->product;
}
daemonize("khidpd_%04x%04x", vendor, product);
set_user_nice(current, -15);
current->flags |= PF_NOFREEZE;
init_waitqueue_entry(&ctrl_wait, current);
init_waitqueue_entry(&intr_wait, current);
add_wait_queue(ctrl_sk->sk_sleep, &ctrl_wait);
add_wait_queue(intr_sk->sk_sleep, &intr_wait);
while (!atomic_read(&session->terminate)) {
set_current_state(TASK_INTERRUPTIBLE);
if (ctrl_sk->sk_state != BT_CONNECTED || intr_sk->sk_state != BT_CONNECTED)
break;
while ((skb = skb_dequeue(&ctrl_sk->sk_receive_queue))) {
skb_orphan(skb);
hidp_recv_ctrl_frame(session, skb);
}
while ((skb = skb_dequeue(&intr_sk->sk_receive_queue))) {
skb_orphan(skb);
hidp_recv_intr_frame(session, skb);
}
hidp_process_transmit(session);
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(intr_sk->sk_sleep, &intr_wait);
remove_wait_queue(ctrl_sk->sk_sleep, &ctrl_wait);
down_write(&hidp_session_sem);
hidp_del_timer(session);
fput(session->intr_sock->file);
wait_event_timeout(*(ctrl_sk->sk_sleep),
(ctrl_sk->sk_state == BT_CLOSED), msecs_to_jiffies(500));
fput(session->ctrl_sock->file);
__hidp_unlink_session(session);
if (session->input) {
input_unregister_device(session->input);
session->input = NULL;
}
if (session->hid) {
if (session->hid->claimed & HID_CLAIMED_INPUT)
hidinput_disconnect(session->hid);
hid_free_device(session->hid);
}
up_write(&hidp_session_sem);
kfree(session);
return 0;
}
static struct device *hidp_get_device(struct hidp_session *session)
{
bdaddr_t *src = &bt_sk(session->ctrl_sock->sk)->src;
bdaddr_t *dst = &bt_sk(session->ctrl_sock->sk)->dst;
struct hci_dev *hdev;
struct hci_conn *conn;
hdev = hci_get_route(dst, src);
if (!hdev)
return NULL;
conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst);
hci_dev_put(hdev);
return conn ? &conn->dev : NULL;
}
static inline void hidp_setup_input(struct hidp_session *session, struct hidp_connadd_req *req)
{
struct input_dev *input = session->input;
int i;
input->private = session;
input->name = "Bluetooth HID Boot Protocol Device";
input->id.bustype = BUS_BLUETOOTH;
input->id.vendor = req->vendor;
input->id.product = req->product;
input->id.version = req->version;
if (req->subclass & 0x40) {
set_bit(EV_KEY, input->evbit);
set_bit(EV_LED, input->evbit);
set_bit(EV_REP, input->evbit);
set_bit(LED_NUML, input->ledbit);
set_bit(LED_CAPSL, input->ledbit);
set_bit(LED_SCROLLL, input->ledbit);
set_bit(LED_COMPOSE, input->ledbit);
set_bit(LED_KANA, input->ledbit);
for (i = 0; i < sizeof(hidp_keycode); i++)
set_bit(hidp_keycode[i], input->keybit);
clear_bit(0, input->keybit);
}
if (req->subclass & 0x80) {
input->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
input->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
input->relbit[0] = BIT(REL_X) | BIT(REL_Y);
input->keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA);
input->relbit[0] |= BIT(REL_WHEEL);
}
input->cdev.dev = hidp_get_device(session);
input->event = hidp_input_event;
input_register_device(input);
}
static int hidp_open(struct hid_device *hid)
{
return 0;
}
static void hidp_close(struct hid_device *hid)
{
}
static const struct {
__u16 idVendor;
__u16 idProduct;
unsigned quirks;
} hidp_blacklist[] = {
/* Apple wireless Mighty Mouse */
{ 0x05ac, 0x030c, HID_QUIRK_MIGHTYMOUSE | HID_QUIRK_INVERT_HWHEEL },
{ } /* Terminating entry */
};
static void hidp_setup_quirks(struct hid_device *hid)
{
unsigned int n;
for (n = 0; hidp_blacklist[n].idVendor; n++)
if (hidp_blacklist[n].idVendor == le16_to_cpu(hid->vendor) &&
hidp_blacklist[n].idProduct == le16_to_cpu(hid->product))
hid->quirks = hidp_blacklist[n].quirks;
}
static inline void hidp_setup_hid(struct hidp_session *session, struct hidp_connadd_req *req)
{
struct hid_device *hid = session->hid;
struct hid_report *report;
bdaddr_t src, dst;
baswap(&src, &bt_sk(session->ctrl_sock->sk)->src);
baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst);
hid->driver_data = session;
hid->country = req->country;
hid->bus = BUS_BLUETOOTH;
hid->vendor = req->vendor;
hid->product = req->product;
hid->version = req->version;
strncpy(hid->name, req->name, 128);
strncpy(hid->phys, batostr(&src), 64);
strncpy(hid->uniq, batostr(&dst), 64);
hid->dev = hidp_get_device(session);
hid->hid_open = hidp_open;
hid->hid_close = hidp_close;
hid->hidinput_input_event = hidp_hidinput_event;
hidp_setup_quirks(hid);
list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list)
hidp_send_report(session, report);
list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list)
hidp_send_report(session, report);
if (hidinput_connect(hid) == 0) {
hid->claimed |= HID_CLAIMED_INPUT;
hid_ff_init(hid);
}
}
int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock)
{
struct hidp_session *session, *s;
int err;
BT_DBG("");
if (bacmp(&bt_sk(ctrl_sock->sk)->src, &bt_sk(intr_sock->sk)->src) ||
bacmp(&bt_sk(ctrl_sock->sk)->dst, &bt_sk(intr_sock->sk)->dst))
return -ENOTUNIQ;
session = kzalloc(sizeof(struct hidp_session), GFP_KERNEL);
if (!session)
return -ENOMEM;
BT_DBG("rd_data %p rd_size %d", req->rd_data, req->rd_size);
if (req->rd_size > 0) {
unsigned char *buf = kmalloc(req->rd_size, GFP_KERNEL);
if (!buf) {
kfree(session);
return -ENOMEM;
}
if (copy_from_user(buf, req->rd_data, req->rd_size)) {
kfree(buf);
kfree(session);
return -EFAULT;
}
session->hid = hid_parse_report(buf, req->rd_size);
kfree(buf);
if (!session->hid) {
kfree(session);
return -EINVAL;
}
}
if (!session->hid) {
session->input = input_allocate_device();
if (!session->input) {
kfree(session);
return -ENOMEM;
}
}
down_write(&hidp_session_sem);
s = __hidp_get_session(&bt_sk(ctrl_sock->sk)->dst);
if (s && s->state == BT_CONNECTED) {
err = -EEXIST;
goto failed;
}
bacpy(&session->bdaddr, &bt_sk(ctrl_sock->sk)->dst);
session->ctrl_mtu = min_t(uint, l2cap_pi(ctrl_sock->sk)->omtu, l2cap_pi(ctrl_sock->sk)->imtu);
session->intr_mtu = min_t(uint, l2cap_pi(intr_sock->sk)->omtu, l2cap_pi(intr_sock->sk)->imtu);
BT_DBG("ctrl mtu %d intr mtu %d", session->ctrl_mtu, session->intr_mtu);
session->ctrl_sock = ctrl_sock;
session->intr_sock = intr_sock;
session->state = BT_CONNECTED;
init_timer(&session->timer);
session->timer.function = hidp_idle_timeout;
session->timer.data = (unsigned long) session;
skb_queue_head_init(&session->ctrl_transmit);
skb_queue_head_init(&session->intr_transmit);
session->flags = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID);
session->idle_to = req->idle_to;
if (session->input)
hidp_setup_input(session, req);
if (session->hid)
hidp_setup_hid(session, req);
__hidp_link_session(session);
hidp_set_timer(session);
err = kernel_thread(hidp_session, session, CLONE_KERNEL);
if (err < 0)
goto unlink;
if (session->input) {
hidp_send_ctrl_message(session,
HIDP_TRANS_SET_PROTOCOL | HIDP_PROTO_BOOT, NULL, 0);
session->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE);
session->leds = 0xff;
hidp_input_event(session->input, EV_LED, 0, 0);
}
up_write(&hidp_session_sem);
return 0;
unlink:
hidp_del_timer(session);
__hidp_unlink_session(session);
if (session->input) {
input_unregister_device(session->input);
session->input = NULL; /* don't try to free it here */
}
failed:
up_write(&hidp_session_sem);
if (session->hid)
hid_free_device(session->hid);
kfree(session->input);
kfree(session);
return err;
}
int hidp_del_connection(struct hidp_conndel_req *req)
{
struct hidp_session *session;
int err = 0;
BT_DBG("");
down_read(&hidp_session_sem);
session = __hidp_get_session(&req->bdaddr);
if (session) {
if (req->flags & (1 << HIDP_VIRTUAL_CABLE_UNPLUG)) {
hidp_send_ctrl_message(session,
HIDP_TRANS_HID_CONTROL | HIDP_CTRL_VIRTUAL_CABLE_UNPLUG, NULL, 0);
} else {
/* Flush the transmit queues */
skb_queue_purge(&session->ctrl_transmit);
skb_queue_purge(&session->intr_transmit);
/* Kill session thread */
atomic_inc(&session->terminate);
hidp_schedule(session);
}
} else
err = -ENOENT;
up_read(&hidp_session_sem);
return err;
}
int hidp_get_connlist(struct hidp_connlist_req *req)
{
struct list_head *p;
int err = 0, n = 0;
BT_DBG("");
down_read(&hidp_session_sem);
list_for_each(p, &hidp_session_list) {
struct hidp_session *session;
struct hidp_conninfo ci;
session = list_entry(p, struct hidp_session, list);
__hidp_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(&hidp_session_sem);
return err;
}
int hidp_get_conninfo(struct hidp_conninfo *ci)
{
struct hidp_session *session;
int err = 0;
down_read(&hidp_session_sem);
session = __hidp_get_session(&ci->bdaddr);
if (session)
__hidp_copy_session(session, ci);
else
err = -ENOENT;
up_read(&hidp_session_sem);
return err;
}
static int __init hidp_init(void)
{
l2cap_load();
BT_INFO("HIDP (Human Interface Emulation) ver %s", VERSION);
return hidp_init_sockets();
}
static void __exit hidp_exit(void)
{
hidp_cleanup_sockets();
}
module_init(hidp_init);
module_exit(hidp_exit);
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Bluetooth HIDP ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");
MODULE_ALIAS("bt-proto-6");

169
net/bluetooth/hidp/hidp.h Normal file
View File

@@ -0,0 +1,169 @@
/*
HIDP implementation for Linux Bluetooth stack (BlueZ).
Copyright (C) 2003-2004 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 __HIDP_H
#define __HIDP_H
#include <linux/types.h>
#include <net/bluetooth/bluetooth.h>
/* HIDP header masks */
#define HIDP_HEADER_TRANS_MASK 0xf0
#define HIDP_HEADER_PARAM_MASK 0x0f
/* HIDP transaction types */
#define HIDP_TRANS_HANDSHAKE 0x00
#define HIDP_TRANS_HID_CONTROL 0x10
#define HIDP_TRANS_GET_REPORT 0x40
#define HIDP_TRANS_SET_REPORT 0x50
#define HIDP_TRANS_GET_PROTOCOL 0x60
#define HIDP_TRANS_SET_PROTOCOL 0x70
#define HIDP_TRANS_GET_IDLE 0x80
#define HIDP_TRANS_SET_IDLE 0x90
#define HIDP_TRANS_DATA 0xa0
#define HIDP_TRANS_DATC 0xb0
/* HIDP handshake results */
#define HIDP_HSHK_SUCCESSFUL 0x00
#define HIDP_HSHK_NOT_READY 0x01
#define HIDP_HSHK_ERR_INVALID_REPORT_ID 0x02
#define HIDP_HSHK_ERR_UNSUPPORTED_REQUEST 0x03
#define HIDP_HSHK_ERR_INVALID_PARAMETER 0x04
#define HIDP_HSHK_ERR_UNKNOWN 0x0e
#define HIDP_HSHK_ERR_FATAL 0x0f
/* HIDP control operation parameters */
#define HIDP_CTRL_NOP 0x00
#define HIDP_CTRL_HARD_RESET 0x01
#define HIDP_CTRL_SOFT_RESET 0x02
#define HIDP_CTRL_SUSPEND 0x03
#define HIDP_CTRL_EXIT_SUSPEND 0x04
#define HIDP_CTRL_VIRTUAL_CABLE_UNPLUG 0x05
/* HIDP data transaction headers */
#define HIDP_DATA_RTYPE_MASK 0x03
#define HIDP_DATA_RSRVD_MASK 0x0c
#define HIDP_DATA_RTYPE_OTHER 0x00
#define HIDP_DATA_RTYPE_INPUT 0x01
#define HIDP_DATA_RTYPE_OUPUT 0x02
#define HIDP_DATA_RTYPE_FEATURE 0x03
/* HIDP protocol header parameters */
#define HIDP_PROTO_BOOT 0x00
#define HIDP_PROTO_REPORT 0x01
/* HIDP ioctl defines */
#define HIDPCONNADD _IOW('H', 200, int)
#define HIDPCONNDEL _IOW('H', 201, int)
#define HIDPGETCONNLIST _IOR('H', 210, int)
#define HIDPGETCONNINFO _IOR('H', 211, int)
#define HIDP_VIRTUAL_CABLE_UNPLUG 0
#define HIDP_BOOT_PROTOCOL_MODE 1
#define HIDP_BLUETOOTH_VENDOR_ID 9
struct hidp_connadd_req {
int ctrl_sock; // Connected control socket
int intr_sock; // Connteted interrupt socket
__u16 parser;
__u16 rd_size;
__u8 __user *rd_data;
__u8 country;
__u8 subclass;
__u16 vendor;
__u16 product;
__u16 version;
__u32 flags;
__u32 idle_to;
char name[128];
};
struct hidp_conndel_req {
bdaddr_t bdaddr;
__u32 flags;
};
struct hidp_conninfo {
bdaddr_t bdaddr;
__u32 flags;
__u16 state;
__u16 vendor;
__u16 product;
__u16 version;
char name[128];
};
struct hidp_connlist_req {
__u32 cnum;
struct hidp_conninfo __user *ci;
};
int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock);
int hidp_del_connection(struct hidp_conndel_req *req);
int hidp_get_connlist(struct hidp_connlist_req *req);
int hidp_get_conninfo(struct hidp_conninfo *ci);
/* HIDP session defines */
struct hidp_session {
struct list_head list;
struct socket *ctrl_sock;
struct socket *intr_sock;
bdaddr_t bdaddr;
unsigned long state;
unsigned long flags;
unsigned long idle_to;
uint ctrl_mtu;
uint intr_mtu;
atomic_t terminate;
unsigned char keys[8];
unsigned char leds;
struct input_dev *input;
struct hid_device *hid;
struct timer_list timer;
struct sk_buff_head ctrl_transmit;
struct sk_buff_head intr_transmit;
};
static inline void hidp_schedule(struct hidp_session *session)
{
struct sock *ctrl_sk = session->ctrl_sock->sk;
struct sock *intr_sk = session->intr_sock->sk;
wake_up_interruptible(ctrl_sk->sk_sleep);
wake_up_interruptible(intr_sk->sk_sleep);
}
/* HIDP init defines */
extern int __init hidp_init_sockets(void);
extern void __exit hidp_cleanup_sockets(void);
#endif /* __HIDP_H */

308
net/bluetooth/hidp/sock.c Normal file
View File

@@ -0,0 +1,308 @@
/*
HIDP implementation for Linux Bluetooth stack (BlueZ).
Copyright (C) 2003-2004 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/module.h>
#include <linux/types.h>
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/kernel.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 <linux/compat.h>
#include <net/sock.h>
#include "hidp.h"
#ifndef CONFIG_BT_HIDP_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
static int hidp_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);
return 0;
}
static int hidp_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *) arg;
struct hidp_connadd_req ca;
struct hidp_conndel_req cd;
struct hidp_connlist_req cl;
struct hidp_conninfo ci;
struct socket *csock;
struct socket *isock;
int err;
BT_DBG("cmd %x arg %lx", cmd, arg);
switch (cmd) {
case HIDPCONNADD:
if (!capable(CAP_NET_ADMIN))
return -EACCES;
if (copy_from_user(&ca, argp, sizeof(ca)))
return -EFAULT;
csock = sockfd_lookup(ca.ctrl_sock, &err);
if (!csock)
return err;
isock = sockfd_lookup(ca.intr_sock, &err);
if (!isock) {
fput(csock->file);
return err;
}
if (csock->sk->sk_state != BT_CONNECTED || isock->sk->sk_state != BT_CONNECTED) {
fput(csock->file);
fput(isock->file);
return -EBADFD;
}
err = hidp_add_connection(&ca, csock, isock);
if (!err) {
if (copy_to_user(argp, &ca, sizeof(ca)))
err = -EFAULT;
} else {
fput(csock->file);
fput(isock->file);
}
return err;
case HIDPCONNDEL:
if (!capable(CAP_NET_ADMIN))
return -EACCES;
if (copy_from_user(&cd, argp, sizeof(cd)))
return -EFAULT;
return hidp_del_connection(&cd);
case HIDPGETCONNLIST:
if (copy_from_user(&cl, argp, sizeof(cl)))
return -EFAULT;
if (cl.cnum <= 0)
return -EINVAL;
err = hidp_get_connlist(&cl);
if (!err && copy_to_user(argp, &cl, sizeof(cl)))
return -EFAULT;
return err;
case HIDPGETCONNINFO:
if (copy_from_user(&ci, argp, sizeof(ci)))
return -EFAULT;
err = hidp_get_conninfo(&ci);
if (!err && copy_to_user(argp, &ci, sizeof(ci)))
return -EFAULT;
return err;
}
return -EINVAL;
}
#ifdef CONFIG_COMPAT
struct compat_hidp_connadd_req {
int ctrl_sock; // Connected control socket
int intr_sock; // Connteted interrupt socket
__u16 parser;
__u16 rd_size;
compat_uptr_t rd_data;
__u8 country;
__u8 subclass;
__u16 vendor;
__u16 product;
__u16 version;
__u32 flags;
__u32 idle_to;
char name[128];
};
static int hidp_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
if (cmd == HIDPGETCONNLIST) {
struct hidp_connlist_req cl;
uint32_t uci;
int err;
if (get_user(cl.cnum, (uint32_t __user *) arg) ||
get_user(uci, (u32 __user *) (arg + 4)))
return -EFAULT;
cl.ci = compat_ptr(uci);
if (cl.cnum <= 0)
return -EINVAL;
err = hidp_get_connlist(&cl);
if (!err && put_user(cl.cnum, (uint32_t __user *) arg))
err = -EFAULT;
return err;
} else if (cmd == HIDPCONNADD) {
struct compat_hidp_connadd_req ca;
struct hidp_connadd_req __user *uca;
uca = compat_alloc_user_space(sizeof(*uca));
if (copy_from_user(&ca, (void __user *) arg, sizeof(ca)))
return -EFAULT;
if (put_user(ca.ctrl_sock, &uca->ctrl_sock) ||
put_user(ca.intr_sock, &uca->intr_sock) ||
put_user(ca.parser, &uca->parser) ||
put_user(ca.rd_size, &uca->rd_size) ||
put_user(compat_ptr(ca.rd_data), &uca->rd_data) ||
put_user(ca.country, &uca->country) ||
put_user(ca.subclass, &uca->subclass) ||
put_user(ca.vendor, &uca->vendor) ||
put_user(ca.product, &uca->product) ||
put_user(ca.version, &uca->version) ||
put_user(ca.flags, &uca->flags) ||
put_user(ca.idle_to, &uca->idle_to) ||
copy_to_user(&uca->name[0], &ca.name[0], 128))
return -EFAULT;
arg = (unsigned long) uca;
/* Fall through. We don't actually write back any _changes_
to the structure anyway, so there's no need to copy back
into the original compat version */
}
return hidp_sock_ioctl(sock, cmd, arg);
}
#endif
static const struct proto_ops hidp_sock_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.release = hidp_sock_release,
.ioctl = hidp_sock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = hidp_sock_compat_ioctl,
#endif
.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 struct proto hidp_proto = {
.name = "HIDP",
.owner = THIS_MODULE,
.obj_size = sizeof(struct bt_sock)
};
static int hidp_sock_create(struct socket *sock, int protocol)
{
struct sock *sk;
BT_DBG("sock %p", sock);
if (sock->type != SOCK_RAW)
return -ESOCKTNOSUPPORT;
sk = sk_alloc(PF_BLUETOOTH, GFP_ATOMIC, &hidp_proto, 1);
if (!sk)
return -ENOMEM;
sock_init_data(sock, sk);
sock->ops = &hidp_sock_ops;
sock->state = SS_UNCONNECTED;
sock_reset_flag(sk, SOCK_ZAPPED);
sk->sk_protocol = protocol;
sk->sk_state = BT_OPEN;
return 0;
}
static struct net_proto_family hidp_sock_family_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.create = hidp_sock_create
};
int __init hidp_init_sockets(void)
{
int err;
err = proto_register(&hidp_proto, 0);
if (err < 0)
return err;
err = bt_sock_register(BTPROTO_HIDP, &hidp_sock_family_ops);
if (err < 0)
goto error;
return 0;
error:
BT_ERR("Can't register HIDP socket");
proto_unregister(&hidp_proto);
return err;
}
void __exit hidp_cleanup_sockets(void)
{
if (bt_sock_unregister(BTPROTO_HIDP) < 0)
BT_ERR("Can't unregister HIDP socket");
proto_unregister(&hidp_proto);
}

2270
net/bluetooth/l2cap.c Normal file

File diff suppressed because it is too large Load Diff

152
net/bluetooth/lib.c Normal file
View File

@@ -0,0 +1,152 @@
/*
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.
*/
/* Bluetooth kernel library. */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/stddef.h>
#include <linux/string.h>
#include <asm/errno.h>
#include <net/bluetooth/bluetooth.h>
void baswap(bdaddr_t *dst, bdaddr_t *src)
{
unsigned char *d = (unsigned char *) dst;
unsigned char *s = (unsigned char *) src;
unsigned int i;
for (i = 0; i < 6; i++)
d[i] = s[5 - i];
}
EXPORT_SYMBOL(baswap);
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];
}
EXPORT_SYMBOL(batostr);
/* Bluetooth error codes to Unix errno mapping */
int bt_err(__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;
}
}
EXPORT_SYMBOL(bt_err);

View File

@@ -0,0 +1,17 @@
config BT_RFCOMM
tristate "RFCOMM protocol support"
depends on BT && BT_L2CAP
help
RFCOMM provides connection oriented stream transport. RFCOMM
support is required for Dialup Networking, OBEX and other Bluetooth
applications.
Say Y here to compile RFCOMM support into the kernel or say M to
compile it as module (rfcomm).
config BT_RFCOMM_TTY
bool "RFCOMM TTY support"
depends on BT_RFCOMM
help
This option enables TTY emulation support for RFCOMM channels.

View File

@@ -0,0 +1,8 @@
#
# Makefile for the Linux Bluetooth RFCOMM layer.
#
obj-$(CONFIG_BT_RFCOMM) += rfcomm.o
rfcomm-y := core.o sock.o
rfcomm-$(CONFIG_BT_RFCOMM_TTY) += tty.o

2113
net/bluetooth/rfcomm/core.c Normal file

File diff suppressed because it is too large Load Diff

972
net/bluetooth/rfcomm/sock.c Normal file
View File

@@ -0,0 +1,972 @@
/*
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 2007/06/12 07:27:14 eyryu Exp $
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/socket.h>
#include <linux/skbuff.h>
#include <linux/list.h>
#include <linux/device.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/l2cap.h>
#include <net/bluetooth/rfcomm.h>
#ifndef CONFIG_BT_RFCOMM_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
static const struct proto_ops rfcomm_sock_ops;
static struct bt_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->sk_rmem_alloc);
skb_queue_tail(&sk->sk_receive_queue, skb);
sk->sk_data_ready(sk, skb->len);
if (atomic_read(&sk->sk_rmem_alloc) >= sk->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->sk_err = err;
sk->sk_state = d->state;
parent = bt_sk(sk)->parent;
if (parent) {
if (d->state == BT_CLOSED) {
sock_set_flag(sk, SOCK_ZAPPED);
bt_accept_unlink(sk);
}
parent->sk_data_ready(parent, 0);
} else {
if (d->state == BT_CONNECTED)
rfcomm_session_getaddr(d->session, &bt_sk(sk)->src, NULL);
sk->sk_state_change(sk);
}
bh_unlock_sock(sk);
if (parent && sock_flag(sk, SOCK_ZAPPED)) {
/* We have to drop DLC lock here, otherwise
* rfcomm_sock_destruct() will dead lock. */
rfcomm_dlc_unlock(d);
rfcomm_sock_kill(sk);
rfcomm_dlc_lock(d);
}
}
/* ---- Socket functions ---- */
static struct sock *__rfcomm_get_sock_by_addr(u8 channel, bdaddr_t *src)
{
struct sock *sk = NULL;
struct hlist_node *node;
sk_for_each(sk, node, &rfcomm_sk_list.head) {
if (rfcomm_pi(sk)->channel == channel &&
!bacmp(&bt_sk(sk)->src, src))
break;
}
return node ? sk : NULL;
}
/* 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 = NULL, *sk1 = NULL;
struct hlist_node *node;
sk_for_each(sk, node, &rfcomm_sk_list.head) {
if (state && sk->sk_state != state)
continue;
if (rfcomm_pi(sk)->channel == channel) {
/* Exact match. */
if (!bacmp(&bt_sk(sk)->src, src))
break;
/* Closest match */
if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY))
sk1 = sk;
}
}
return node ? 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->sk_receive_queue);
skb_queue_purge(&sk->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);
}
static void rfcomm_sock_cleanup_listen(struct sock *parent)
{
struct sock *sk;
BT_DBG("parent %p", parent);
/* Close not yet accepted dlcs */
while ((sk = bt_accept_dequeue(parent, NULL))) {
rfcomm_sock_close(sk);
rfcomm_sock_kill(sk);
}
parent->sk_state = BT_CLOSED;
sock_set_flag(parent, SOCK_ZAPPED);
}
/* Kill socket (only if zapped and orphan)
* Must be called on unlocked socket.
*/
static void rfcomm_sock_kill(struct sock *sk)
{
if (!sock_flag(sk, SOCK_ZAPPED) || sk->sk_socket)
return;
BT_DBG("sk %p state %d refcnt %d", sk, sk->sk_state, atomic_read(&sk->sk_refcnt));
/* Kill poor orphan */
bt_sock_unlink(&rfcomm_sk_list, sk);
sock_set_flag(sk, SOCK_DEAD);
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->sk_state, sk->sk_socket);
switch (sk->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:
sock_set_flag(sk, SOCK_ZAPPED);
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)
{
struct rfcomm_pinfo *pi = rfcomm_pi(sk);
BT_DBG("sk %p", sk);
if (parent) {
sk->sk_type = parent->sk_type;
pi->link_mode = rfcomm_pi(parent)->link_mode;
} else {
pi->link_mode = 0;
}
pi->dlc->link_mode = pi->link_mode;
}
static struct proto rfcomm_proto = {
.name = "RFCOMM",
.owner = THIS_MODULE,
.obj_size = sizeof(struct rfcomm_pinfo)
};
static struct sock *rfcomm_sock_alloc(struct socket *sock, int proto, gfp_t prio)
{
struct rfcomm_dlc *d;
struct sock *sk;
sk = sk_alloc(PF_BLUETOOTH, prio, &rfcomm_proto, 1);
if (!sk)
return NULL;
sock_init_data(sock, sk);
INIT_LIST_HEAD(&bt_sk(sk)->accept_q);
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;
sk->sk_destruct = rfcomm_sock_destruct;
sk->sk_sndtimeo = RFCOMM_CONN_TIMEOUT;
sk->sk_sndbuf = RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 10;
sk->sk_rcvbuf = RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 10;
sock_reset_flag(sk, SOCK_ZAPPED);
sk->sk_protocol = proto;
sk->sk_state = BT_OPEN;
bt_sock_link(&rfcomm_sk_list, sk);
BT_DBG("sk %p", sk);
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;
sk = rfcomm_sock_alloc(sock, protocol, GFP_ATOMIC);
if (!sk)
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->sk_state != BT_OPEN) {
err = -EBADFD;
goto done;
}
if (sk->sk_type != SOCK_STREAM) {
err = -EINVAL;
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(&bt_sk(sk)->src, &sa->rc_bdaddr);
rfcomm_pi(sk)->channel = sa->rc_channel;
sk->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;
lock_sock(sk);
if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) {
err = -EBADFD;
goto done;
}
if (sk->sk_type != SOCK_STREAM) {
err = -EINVAL;
goto done;
}
sk->sk_state = BT_CONNECT;
bacpy(&bt_sk(sk)->dst, &sa->rc_bdaddr);
rfcomm_pi(sk)->channel = sa->rc_channel;
err = rfcomm_dlc_open(d, &bt_sk(sk)->src, &sa->rc_bdaddr, sa->rc_channel);
if (!err)
err = bt_sock_wait_state(sk, BT_CONNECTED,
sock_sndtimeo(sk, flags & O_NONBLOCK));
done:
release_sock(sk);
return err;
}
static 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->sk_state != BT_BOUND) {
err = -EBADFD;
goto done;
}
if (sk->sk_type != SOCK_STREAM) {
err = -EINVAL;
goto done;
}
if (!rfcomm_pi(sk)->channel) {
bdaddr_t *src = &bt_sk(sk)->src;
u8 channel;
err = -EINVAL;
write_lock_bh(&rfcomm_sk_list.lock);
for (channel = 1; channel < 31; channel++)
if (!__rfcomm_get_sock_by_addr(channel, src)) {
rfcomm_pi(sk)->channel = channel;
err = 0;
break;
}
write_unlock_bh(&rfcomm_sk_list.lock);
if (err < 0)
goto done;
}
sk->sk_max_ack_backlog = backlog;
sk->sk_ack_backlog = 0;
sk->sk_state = BT_LISTEN;
done:
release_sock(sk);
return err;
}
static 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->sk_state != BT_LISTEN) {
err = -EBADFD;
goto done;
}
if (sk->sk_type != SOCK_STREAM) {
err = -EINVAL;
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->sk_sleep, &wait);
while (!(nsk = bt_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->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->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, &bt_sk(sk)->dst);
else
bacpy(&sa->rc_bdaddr, &bt_sk(sk)->src);
*len = sizeof(struct sockaddr_rc);
return 0;
}
static int rfcomm_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t len)
{
struct sock *sk = sock->sk;
struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc;
struct sk_buff *skb;
int sent = 0;
if (msg->msg_flags & MSG_OOB)
return -EOPNOTSUPP;
if (sk->sk_shutdown & SEND_SHUTDOWN)
return -EPIPE;
BT_DBG("sock %p, sk %p", sock, sk);
lock_sock(sk);
while (len) {
size_t size = min_t(size_t, len, d->mtu);
int err;
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);
if (sent == 0)
sent = err;
break;
}
err = rfcomm_dlc_send(d, skb);
if (err < 0) {
kfree_skb(skb);
if (sent == 0)
sent = err;
break;
}
sent += size;
len -= size;
}
release_sock(sk);
return sent;
}
static long rfcomm_sock_data_wait(struct sock *sk, long timeo)
{
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(sk->sk_sleep, &wait);
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (!skb_queue_empty(&sk->sk_receive_queue) ||
sk->sk_err ||
(sk->sk_shutdown & RCV_SHUTDOWN) ||
signal_pending(current) ||
!timeo)
break;
set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
release_sock(sk);
timeo = schedule_timeout(timeo);
lock_sock(sk);
clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(sk->sk_sleep, &wait);
return timeo;
}
static int rfcomm_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
struct sock *sk = sock->sk;
int err = 0;
size_t target, 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->sk_receive_queue);
if (!skb) {
if (copied >= target)
break;
if ((err = sock_error(sk)) != 0)
break;
if (sk->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->sk_receive_queue, skb);
if (!copied)
copied = -EFAULT;
break;
}
copied += chunk;
size -= chunk;
if (!(flags & MSG_PEEK)) {
atomic_sub(chunk, &sk->sk_rmem_alloc);
skb_pull(skb, chunk);
if (skb->len) {
skb_queue_head(&sk->sk_receive_queue, skb);
break;
}
kfree_skb(skb);
} else {
/* put message back and return */
skb_queue_head(&sk->sk_receive_queue, skb);
break;
}
} while (size);
out:
if (atomic_read(&sk->sk_rmem_alloc) <= (sk->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 __user *optval, int optlen)
{
struct sock *sk = sock->sk;
int err = 0;
u32 opt;
BT_DBG("sk %p", sk);
lock_sock(sk);
switch (optname) {
case RFCOMM_LM:
if (get_user(opt, (u32 __user *) optval)) {
err = -EFAULT;
break;
}
rfcomm_pi(sk)->link_mode = opt;
break;
default:
err = -ENOPROTOOPT;
break;
}
release_sock(sk);
return err;
}
static int rfcomm_sock_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
{
struct sock *sk = sock->sk;
struct sock *l2cap_sk;
struct rfcomm_conninfo cinfo;
int len, err = 0;
BT_DBG("sk %p", sk);
if (get_user(len, optlen))
return -EFAULT;
lock_sock(sk);
switch (optname) {
case RFCOMM_LM:
if (put_user(rfcomm_pi(sk)->link_mode, (u32 __user *) optval))
err = -EFAULT;
break;
case RFCOMM_CONNINFO:
if (sk->sk_state != BT_CONNECTED) {
err = -ENOTCONN;
break;
}
l2cap_sk = rfcomm_pi(sk)->dlc->session->sock->sk;
cinfo.hci_handle = l2cap_pi(l2cap_sk)->conn->hcon->handle;
memcpy(cinfo.dev_class, l2cap_pi(l2cap_sk)->conn->hcon->dev_class, 3);
len = min_t(unsigned int, 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 rfcomm_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
struct sock *sk = sock->sk;
int err;
lock_sock(sk);
#ifdef CONFIG_BT_RFCOMM_TTY
err = rfcomm_dev_ioctl(sk, cmd, (void __user *)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->sk_shutdown) {
sk->sk_shutdown = SHUTDOWN_MASK;
__rfcomm_sock_close(sk);
if (sock_flag(sk, SOCK_LINGER) && sk->sk_lingertime)
err = bt_sock_wait_state(sk, BT_CLOSED, sk->sk_lingertime);
}
release_sock(sk);
return err;
}
static int rfcomm_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 = 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 channel */
parent = rfcomm_get_sock_by_channel(BT_LISTEN, channel, &src);
if (!parent)
return 0;
/* Check for backlog size */
if (sk_acceptq_is_full(parent)) {
BT_DBG("backlog full %d", parent->sk_ack_backlog);
goto done;
}
sk = rfcomm_sock_alloc(NULL, BTPROTO_RFCOMM, GFP_ATOMIC);
if (!sk)
goto done;
rfcomm_sock_init(sk, parent);
bacpy(&bt_sk(sk)->src, &src);
bacpy(&bt_sk(sk)->dst, &dst);
rfcomm_pi(sk)->channel = channel;
sk->sk_state = BT_CONFIG;
bt_accept_enqueue(parent, sk);
/* Accept connection and return socket DLC */
*d = rfcomm_pi(sk)->dlc;
result = 1;
done:
bh_unlock_sock(parent);
return result;
}
static ssize_t rfcomm_sock_sysfs_show(struct class *dev, char *buf)
{
struct sock *sk;
struct hlist_node *node;
char *str = buf;
read_lock_bh(&rfcomm_sk_list.lock);
sk_for_each(sk, node, &rfcomm_sk_list.head) {
str += sprintf(str, "%s %s %d %d\n",
batostr(&bt_sk(sk)->src), batostr(&bt_sk(sk)->dst),
sk->sk_state, rfcomm_pi(sk)->channel);
}
read_unlock_bh(&rfcomm_sk_list.lock);
return (str - buf);
}
static CLASS_ATTR(rfcomm, S_IRUGO, rfcomm_sock_sysfs_show, NULL);
static const struct proto_ops rfcomm_sock_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.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 = bt_sock_poll,
.socketpair = sock_no_socketpair,
.mmap = sock_no_mmap
};
static struct net_proto_family rfcomm_sock_family_ops = {
.family = PF_BLUETOOTH,
.owner = THIS_MODULE,
.create = rfcomm_sock_create
};
int __init rfcomm_init_sockets(void)
{
int err;
err = proto_register(&rfcomm_proto, 0);
if (err < 0)
return err;
err = bt_sock_register(BTPROTO_RFCOMM, &rfcomm_sock_family_ops);
if (err < 0)
goto error;
if (class_create_file(bt_class, &class_attr_rfcomm) < 0)
BT_ERR("Failed to create RFCOMM info file");
BT_INFO("RFCOMM socket layer initialized");
return 0;
error:
BT_ERR("RFCOMM socket layer registration failed");
proto_unregister(&rfcomm_proto);
return err;
}
void __exit rfcomm_cleanup_sockets(void)
{
class_remove_file(bt_class, &class_attr_rfcomm);
if (bt_sock_unregister(BTPROTO_RFCOMM) < 0)
BT_ERR("RFCOMM socket layer unregistration failed");
proto_unregister(&rfcomm_proto);
}

1082
net/bluetooth/rfcomm/tty.c Normal file

File diff suppressed because it is too large Load Diff

1004
net/bluetooth/sco.c Normal file

File diff suppressed because it is too large Load Diff