mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
1421
net/bluetooth/6lowpan.c
Normal file
1421
net/bluetooth/6lowpan.c
Normal file
File diff suppressed because it is too large
Load diff
56
net/bluetooth/Kconfig
Normal file
56
net/bluetooth/Kconfig
Normal file
|
@ -0,0 +1,56 @@
|
|||
#
|
||||
# Bluetooth subsystem configuration
|
||||
#
|
||||
|
||||
menuconfig BT
|
||||
tristate "Bluetooth subsystem support"
|
||||
depends on NET && !S390
|
||||
depends on RFKILL || !RFKILL
|
||||
select CRC16
|
||||
select CRYPTO
|
||||
select CRYPTO_BLKCIPHER
|
||||
select CRYPTO_AES
|
||||
select CRYPTO_ECB
|
||||
select CRYPTO_SHA256
|
||||
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
|
||||
SCO audio links
|
||||
L2CAP (Logical Link Control and Adaptation Protocol)
|
||||
SMP (Security Manager Protocol) on LE (Low Energy) links
|
||||
HCI Device drivers (Interface to the hardware)
|
||||
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 bluetoothd. These utilities and updates
|
||||
to Bluetooth kernel modules are provided in the BlueZ packages. For
|
||||
more information, see <http://www.bluez.org/>.
|
||||
|
||||
config BT_6LOWPAN
|
||||
tristate "Bluetooth 6LoWPAN support"
|
||||
depends on BT && 6LOWPAN
|
||||
help
|
||||
IPv6 compression over Bluetooth Low Energy.
|
||||
|
||||
source "net/bluetooth/rfcomm/Kconfig"
|
||||
|
||||
source "net/bluetooth/bnep/Kconfig"
|
||||
|
||||
source "net/bluetooth/cmtp/Kconfig"
|
||||
|
||||
source "net/bluetooth/hidp/Kconfig"
|
||||
|
||||
source "drivers/bluetooth/Kconfig"
|
18
net/bluetooth/Makefile
Normal file
18
net/bluetooth/Makefile
Normal file
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Makefile for the Linux Bluetooth subsystem.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_BT) += bluetooth.o
|
||||
obj-$(CONFIG_BT_RFCOMM) += rfcomm/
|
||||
obj-$(CONFIG_BT_BNEP) += bnep/
|
||||
obj-$(CONFIG_BT_CMTP) += cmtp/
|
||||
obj-$(CONFIG_BT_HIDP) += hidp/
|
||||
obj-$(CONFIG_BT_6LOWPAN) += bluetooth_6lowpan.o
|
||||
|
||||
bluetooth_6lowpan-y := 6lowpan.o
|
||||
|
||||
bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \
|
||||
hci_sock.o hci_sysfs.o l2cap_core.o l2cap_sock.o smp.o sco.o lib.o \
|
||||
a2mp.o amp.o
|
||||
|
||||
subdir-ccflags-y += -D__CHECK_ENDIAN__
|
1025
net/bluetooth/a2mp.c
Normal file
1025
net/bluetooth/a2mp.c
Normal file
File diff suppressed because it is too large
Load diff
150
net/bluetooth/a2mp.h
Normal file
150
net/bluetooth/a2mp.h
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
Copyright (c) 2010,2011 Code Aurora Forum. All rights reserved.
|
||||
Copyright (c) 2011,2012 Intel Corp.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 and
|
||||
only 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.
|
||||
*/
|
||||
|
||||
#ifndef __A2MP_H
|
||||
#define __A2MP_H
|
||||
|
||||
#include <net/bluetooth/l2cap.h>
|
||||
|
||||
#define A2MP_FEAT_EXT 0x8000
|
||||
|
||||
enum amp_mgr_state {
|
||||
READ_LOC_AMP_INFO,
|
||||
READ_LOC_AMP_ASSOC,
|
||||
READ_LOC_AMP_ASSOC_FINAL,
|
||||
WRITE_REMOTE_AMP_ASSOC,
|
||||
};
|
||||
|
||||
struct amp_mgr {
|
||||
struct list_head list;
|
||||
struct l2cap_conn *l2cap_conn;
|
||||
struct l2cap_chan *a2mp_chan;
|
||||
struct l2cap_chan *bredr_chan;
|
||||
struct kref kref;
|
||||
__u8 ident;
|
||||
__u8 handle;
|
||||
unsigned long state;
|
||||
unsigned long flags;
|
||||
|
||||
struct list_head amp_ctrls;
|
||||
struct mutex amp_ctrls_lock;
|
||||
};
|
||||
|
||||
struct a2mp_cmd {
|
||||
__u8 code;
|
||||
__u8 ident;
|
||||
__le16 len;
|
||||
__u8 data[0];
|
||||
} __packed;
|
||||
|
||||
/* A2MP command codes */
|
||||
#define A2MP_COMMAND_REJ 0x01
|
||||
struct a2mp_cmd_rej {
|
||||
__le16 reason;
|
||||
__u8 data[0];
|
||||
} __packed;
|
||||
|
||||
#define A2MP_DISCOVER_REQ 0x02
|
||||
struct a2mp_discov_req {
|
||||
__le16 mtu;
|
||||
__le16 ext_feat;
|
||||
} __packed;
|
||||
|
||||
struct a2mp_cl {
|
||||
__u8 id;
|
||||
__u8 type;
|
||||
__u8 status;
|
||||
} __packed;
|
||||
|
||||
#define A2MP_DISCOVER_RSP 0x03
|
||||
struct a2mp_discov_rsp {
|
||||
__le16 mtu;
|
||||
__le16 ext_feat;
|
||||
struct a2mp_cl cl[0];
|
||||
} __packed;
|
||||
|
||||
#define A2MP_CHANGE_NOTIFY 0x04
|
||||
#define A2MP_CHANGE_RSP 0x05
|
||||
|
||||
#define A2MP_GETINFO_REQ 0x06
|
||||
struct a2mp_info_req {
|
||||
__u8 id;
|
||||
} __packed;
|
||||
|
||||
#define A2MP_GETINFO_RSP 0x07
|
||||
struct a2mp_info_rsp {
|
||||
__u8 id;
|
||||
__u8 status;
|
||||
__le32 total_bw;
|
||||
__le32 max_bw;
|
||||
__le32 min_latency;
|
||||
__le16 pal_cap;
|
||||
__le16 assoc_size;
|
||||
} __packed;
|
||||
|
||||
#define A2MP_GETAMPASSOC_REQ 0x08
|
||||
struct a2mp_amp_assoc_req {
|
||||
__u8 id;
|
||||
} __packed;
|
||||
|
||||
#define A2MP_GETAMPASSOC_RSP 0x09
|
||||
struct a2mp_amp_assoc_rsp {
|
||||
__u8 id;
|
||||
__u8 status;
|
||||
__u8 amp_assoc[0];
|
||||
} __packed;
|
||||
|
||||
#define A2MP_CREATEPHYSLINK_REQ 0x0A
|
||||
#define A2MP_DISCONNPHYSLINK_REQ 0x0C
|
||||
struct a2mp_physlink_req {
|
||||
__u8 local_id;
|
||||
__u8 remote_id;
|
||||
__u8 amp_assoc[0];
|
||||
} __packed;
|
||||
|
||||
#define A2MP_CREATEPHYSLINK_RSP 0x0B
|
||||
#define A2MP_DISCONNPHYSLINK_RSP 0x0D
|
||||
struct a2mp_physlink_rsp {
|
||||
__u8 local_id;
|
||||
__u8 remote_id;
|
||||
__u8 status;
|
||||
} __packed;
|
||||
|
||||
/* A2MP response status */
|
||||
#define A2MP_STATUS_SUCCESS 0x00
|
||||
#define A2MP_STATUS_INVALID_CTRL_ID 0x01
|
||||
#define A2MP_STATUS_UNABLE_START_LINK_CREATION 0x02
|
||||
#define A2MP_STATUS_NO_PHYSICAL_LINK_EXISTS 0x02
|
||||
#define A2MP_STATUS_COLLISION_OCCURED 0x03
|
||||
#define A2MP_STATUS_DISCONN_REQ_RECVD 0x04
|
||||
#define A2MP_STATUS_PHYS_LINK_EXISTS 0x05
|
||||
#define A2MP_STATUS_SECURITY_VIOLATION 0x06
|
||||
|
||||
extern struct list_head amp_mgr_list;
|
||||
extern struct mutex amp_mgr_list_lock;
|
||||
|
||||
struct amp_mgr *amp_mgr_get(struct amp_mgr *mgr);
|
||||
int amp_mgr_put(struct amp_mgr *mgr);
|
||||
u8 __next_ident(struct amp_mgr *mgr);
|
||||
struct l2cap_chan *a2mp_channel_create(struct l2cap_conn *conn,
|
||||
struct sk_buff *skb);
|
||||
struct amp_mgr *amp_mgr_lookup_by_state(u8 state);
|
||||
void a2mp_send(struct amp_mgr *mgr, u8 code, u8 ident, u16 len, void *data);
|
||||
void a2mp_discover_amp(struct l2cap_chan *chan);
|
||||
void a2mp_send_getinfo_rsp(struct hci_dev *hdev);
|
||||
void a2mp_send_getampassoc_rsp(struct hci_dev *hdev, u8 status);
|
||||
void a2mp_send_create_phy_link_req(struct hci_dev *hdev, u8 status);
|
||||
void a2mp_send_create_phy_link_rsp(struct hci_dev *hdev, u8 status);
|
||||
|
||||
#endif /* __A2MP_H */
|
810
net/bluetooth/af_bluetooth.c
Normal file
810
net/bluetooth/af_bluetooth.c
Normal file
|
@ -0,0 +1,810 @@
|
|||
/*
|
||||
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/debugfs.h>
|
||||
#include <asm/ioctls.h>
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <linux/proc_fs.h>
|
||||
|
||||
#define VERSION "2.19"
|
||||
|
||||
/* Bluetooth sockets */
|
||||
#define BT_MAX_PROTO 8
|
||||
static const struct net_proto_family *bt_proto[BT_MAX_PROTO];
|
||||
static DEFINE_RWLOCK(bt_proto_lock);
|
||||
|
||||
static struct lock_class_key bt_lock_key[BT_MAX_PROTO];
|
||||
static const char *const bt_key_strings[BT_MAX_PROTO] = {
|
||||
"sk_lock-AF_BLUETOOTH-BTPROTO_L2CAP",
|
||||
"sk_lock-AF_BLUETOOTH-BTPROTO_HCI",
|
||||
"sk_lock-AF_BLUETOOTH-BTPROTO_SCO",
|
||||
"sk_lock-AF_BLUETOOTH-BTPROTO_RFCOMM",
|
||||
"sk_lock-AF_BLUETOOTH-BTPROTO_BNEP",
|
||||
"sk_lock-AF_BLUETOOTH-BTPROTO_CMTP",
|
||||
"sk_lock-AF_BLUETOOTH-BTPROTO_HIDP",
|
||||
"sk_lock-AF_BLUETOOTH-BTPROTO_AVDTP",
|
||||
};
|
||||
|
||||
static struct lock_class_key bt_slock_key[BT_MAX_PROTO];
|
||||
static const char *const bt_slock_key_strings[BT_MAX_PROTO] = {
|
||||
"slock-AF_BLUETOOTH-BTPROTO_L2CAP",
|
||||
"slock-AF_BLUETOOTH-BTPROTO_HCI",
|
||||
"slock-AF_BLUETOOTH-BTPROTO_SCO",
|
||||
"slock-AF_BLUETOOTH-BTPROTO_RFCOMM",
|
||||
"slock-AF_BLUETOOTH-BTPROTO_BNEP",
|
||||
"slock-AF_BLUETOOTH-BTPROTO_CMTP",
|
||||
"slock-AF_BLUETOOTH-BTPROTO_HIDP",
|
||||
"slock-AF_BLUETOOTH-BTPROTO_AVDTP",
|
||||
};
|
||||
|
||||
void bt_sock_reclassify_lock(struct sock *sk, int proto)
|
||||
{
|
||||
BUG_ON(!sk);
|
||||
BUG_ON(sock_owned_by_user(sk));
|
||||
|
||||
sock_lock_init_class_and_name(sk,
|
||||
bt_slock_key_strings[proto], &bt_slock_key[proto],
|
||||
bt_key_strings[proto], &bt_lock_key[proto]);
|
||||
}
|
||||
EXPORT_SYMBOL(bt_sock_reclassify_lock);
|
||||
|
||||
int bt_sock_register(int proto, const 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);
|
||||
|
||||
void bt_sock_unregister(int proto)
|
||||
{
|
||||
if (proto < 0 || proto >= BT_MAX_PROTO)
|
||||
return;
|
||||
|
||||
write_lock(&bt_proto_lock);
|
||||
bt_proto[proto] = NULL;
|
||||
write_unlock(&bt_proto_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(bt_sock_unregister);
|
||||
|
||||
#ifdef CONFIG_PARANOID_NETWORK
|
||||
static inline int current_has_bt_admin(void)
|
||||
{
|
||||
return !current_euid();
|
||||
}
|
||||
|
||||
static inline int current_has_bt(void)
|
||||
{
|
||||
return current_has_bt_admin();
|
||||
}
|
||||
# else
|
||||
static inline int current_has_bt_admin(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int current_has_bt(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int bt_sock_create(struct net *net, struct socket *sock, int proto,
|
||||
int kern)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (proto == BTPROTO_RFCOMM || proto == BTPROTO_SCO ||
|
||||
proto == BTPROTO_L2CAP) {
|
||||
if (!current_has_bt())
|
||||
return -EPERM;
|
||||
} else if (!current_has_bt_admin())
|
||||
return -EPERM;
|
||||
|
||||
if (net != &init_net)
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
if (proto < 0 || proto >= BT_MAX_PROTO)
|
||||
return -EINVAL;
|
||||
|
||||
if (!bt_proto[proto])
|
||||
request_module("bt-proto-%d", proto);
|
||||
|
||||
err = -EPROTONOSUPPORT;
|
||||
|
||||
read_lock(&bt_proto_lock);
|
||||
|
||||
if (bt_proto[proto] && try_module_get(bt_proto[proto]->owner)) {
|
||||
err = bt_proto[proto]->create(net, sock, proto, kern);
|
||||
if (!err)
|
||||
bt_sock_reclassify_lock(sock->sk, 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(&l->lock);
|
||||
sk_add_node(sk, &l->head);
|
||||
write_unlock(&l->lock);
|
||||
}
|
||||
EXPORT_SYMBOL(bt_sock_link);
|
||||
|
||||
void bt_sock_unlink(struct bt_sock_list *l, struct sock *sk)
|
||||
{
|
||||
write_lock(&l->lock);
|
||||
sk_del_node_init(sk);
|
||||
write_unlock(&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 ||
|
||||
test_bit(BT_SK_DEFER_SETUP, &bt_sk(parent)->flags)) {
|
||||
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 %zu", sock, sk, len);
|
||||
|
||||
if (flags & (MSG_OOB))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
skb = skb_recv_datagram(sk, flags, noblock, &err);
|
||||
if (!skb) {
|
||||
if (sk->sk_shutdown & RCV_SHUTDOWN)
|
||||
return 0;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
copied = skb->len;
|
||||
if (len < copied) {
|
||||
msg->msg_flags |= MSG_TRUNC;
|
||||
copied = len;
|
||||
}
|
||||
|
||||
skb_reset_transport_header(skb);
|
||||
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
|
||||
if (err == 0) {
|
||||
sock_recv_ts_and_drops(msg, sk, skb);
|
||||
|
||||
if (bt_sk(sk)->skb_msg_name)
|
||||
bt_sk(sk)->skb_msg_name(skb, msg->msg_name,
|
||||
&msg->msg_namelen);
|
||||
}
|
||||
|
||||
skb_free_datagram(sk, skb);
|
||||
|
||||
return err ? : copied;
|
||||
}
|
||||
EXPORT_SYMBOL(bt_sock_recvmsg);
|
||||
|
||||
static long bt_sock_data_wait(struct sock *sk, long timeo)
|
||||
{
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
add_wait_queue(sk_sleep(sk), &wait);
|
||||
for (;;) {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
if (!skb_queue_empty(&sk->sk_receive_queue))
|
||||
break;
|
||||
|
||||
if (sk->sk_err || (sk->sk_shutdown & RCV_SHUTDOWN))
|
||||
break;
|
||||
|
||||
if (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_sleep(sk), &wait);
|
||||
return timeo;
|
||||
}
|
||||
|
||||
int bt_sock_stream_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;
|
||||
|
||||
BT_DBG("sk %p size %zu", 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;
|
||||
|
||||
err = sock_error(sk);
|
||||
if (err)
|
||||
break;
|
||||
if (sk->sk_shutdown & RCV_SHUTDOWN)
|
||||
break;
|
||||
|
||||
err = -EAGAIN;
|
||||
if (!timeo)
|
||||
break;
|
||||
|
||||
timeo = bt_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 (skb_copy_datagram_iovec(skb, 0, msg->msg_iov, chunk)) {
|
||||
skb_queue_head(&sk->sk_receive_queue, skb);
|
||||
if (!copied)
|
||||
copied = -EFAULT;
|
||||
break;
|
||||
}
|
||||
copied += chunk;
|
||||
size -= chunk;
|
||||
|
||||
sock_recv_ts_and_drops(msg, sk, skb);
|
||||
|
||||
if (!(flags & MSG_PEEK)) {
|
||||
int skb_len = skb_headlen(skb);
|
||||
|
||||
if (chunk <= skb_len) {
|
||||
__skb_pull(skb, chunk);
|
||||
} else {
|
||||
struct sk_buff *frag;
|
||||
|
||||
__skb_pull(skb, skb_len);
|
||||
chunk -= skb_len;
|
||||
|
||||
skb_walk_frags(skb, frag) {
|
||||
if (chunk <= frag->len) {
|
||||
/* Pulling partial data */
|
||||
skb->len -= chunk;
|
||||
skb->data_len -= chunk;
|
||||
__skb_pull(frag, chunk);
|
||||
break;
|
||||
} else if (frag->len) {
|
||||
/* Pulling all frag data */
|
||||
chunk -= frag->len;
|
||||
skb->len -= frag->len;
|
||||
skb->data_len -= frag->len;
|
||||
__skb_pull(frag, frag->len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
release_sock(sk);
|
||||
return copied ? : err;
|
||||
}
|
||||
EXPORT_SYMBOL(bt_sock_stream_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 ||
|
||||
(test_bit(BT_SK_DEFER_SETUP, &bt_sk(parent)->flags) &&
|
||||
sk->sk_state == BT_CONNECT2))
|
||||
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_sleep(sk), 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 |
|
||||
(sock_flag(sk, SOCK_SELECT_ERR_QUEUE) ? POLLPRI : 0);
|
||||
|
||||
if (sk->sk_shutdown & RCV_SHUTDOWN)
|
||||
mask |= POLLRDHUP | POLLIN | POLLRDNORM;
|
||||
|
||||
if (sk->sk_shutdown == SHUTDOWN_MASK)
|
||||
mask |= POLLHUP;
|
||||
|
||||
if (!skb_queue_empty(&sk->sk_receive_queue))
|
||||
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 (!test_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags) && 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_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct sk_buff *skb;
|
||||
long amount;
|
||||
int err;
|
||||
|
||||
BT_DBG("sk %p cmd %x arg %lx", sk, cmd, arg);
|
||||
|
||||
switch (cmd) {
|
||||
case TIOCOUTQ:
|
||||
if (sk->sk_state == BT_LISTEN)
|
||||
return -EINVAL;
|
||||
|
||||
amount = sk->sk_sndbuf - sk_wmem_alloc_get(sk);
|
||||
if (amount < 0)
|
||||
amount = 0;
|
||||
err = put_user(amount, (int __user *) arg);
|
||||
break;
|
||||
|
||||
case TIOCINQ:
|
||||
if (sk->sk_state == BT_LISTEN)
|
||||
return -EINVAL;
|
||||
|
||||
lock_sock(sk);
|
||||
skb = skb_peek(&sk->sk_receive_queue);
|
||||
amount = skb ? skb->len : 0;
|
||||
release_sock(sk);
|
||||
err = put_user(amount, (int __user *) arg);
|
||||
break;
|
||||
|
||||
case SIOCGSTAMP:
|
||||
err = sock_get_timestamp(sk, (struct timeval __user *) arg);
|
||||
break;
|
||||
|
||||
case SIOCGSTAMPNS:
|
||||
err = sock_get_timestampns(sk, (struct timespec __user *) arg);
|
||||
break;
|
||||
|
||||
default:
|
||||
err = -ENOIOCTLCMD;
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(bt_sock_ioctl);
|
||||
|
||||
/* This function expects the sk lock to be held when called */
|
||||
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_sleep(sk), &wait);
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
while (sk->sk_state != state) {
|
||||
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);
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
err = sock_error(sk);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(sk_sleep(sk), &wait);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(bt_sock_wait_state);
|
||||
|
||||
/* This function expects the sk lock to be held when called */
|
||||
int bt_sock_wait_ready(struct sock *sk, unsigned long flags)
|
||||
{
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
unsigned long timeo;
|
||||
int err = 0;
|
||||
|
||||
BT_DBG("sk %p", sk);
|
||||
|
||||
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
|
||||
|
||||
add_wait_queue(sk_sleep(sk), &wait);
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
while (test_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags)) {
|
||||
if (!timeo) {
|
||||
err = -EAGAIN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (signal_pending(current)) {
|
||||
err = sock_intr_errno(timeo);
|
||||
break;
|
||||
}
|
||||
|
||||
release_sock(sk);
|
||||
timeo = schedule_timeout(timeo);
|
||||
lock_sock(sk);
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
err = sock_error(sk);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(sk_sleep(sk), &wait);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(bt_sock_wait_ready);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
struct bt_seq_state {
|
||||
struct bt_sock_list *l;
|
||||
};
|
||||
|
||||
static void *bt_seq_start(struct seq_file *seq, loff_t *pos)
|
||||
__acquires(seq->private->l->lock)
|
||||
{
|
||||
struct bt_seq_state *s = seq->private;
|
||||
struct bt_sock_list *l = s->l;
|
||||
|
||||
read_lock(&l->lock);
|
||||
return seq_hlist_start_head(&l->head, *pos);
|
||||
}
|
||||
|
||||
static void *bt_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
||||
{
|
||||
struct bt_seq_state *s = seq->private;
|
||||
struct bt_sock_list *l = s->l;
|
||||
|
||||
return seq_hlist_next(v, &l->head, pos);
|
||||
}
|
||||
|
||||
static void bt_seq_stop(struct seq_file *seq, void *v)
|
||||
__releases(seq->private->l->lock)
|
||||
{
|
||||
struct bt_seq_state *s = seq->private;
|
||||
struct bt_sock_list *l = s->l;
|
||||
|
||||
read_unlock(&l->lock);
|
||||
}
|
||||
|
||||
static int bt_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct bt_seq_state *s = seq->private;
|
||||
struct bt_sock_list *l = s->l;
|
||||
|
||||
if (v == SEQ_START_TOKEN) {
|
||||
seq_puts(seq ,"sk RefCnt Rmem Wmem User Inode Parent");
|
||||
|
||||
if (l->custom_seq_show) {
|
||||
seq_putc(seq, ' ');
|
||||
l->custom_seq_show(seq, v);
|
||||
}
|
||||
|
||||
seq_putc(seq, '\n');
|
||||
} else {
|
||||
struct sock *sk = sk_entry(v);
|
||||
struct bt_sock *bt = bt_sk(sk);
|
||||
|
||||
seq_printf(seq,
|
||||
"%pK %-6d %-6u %-6u %-6u %-6lu %-6lu",
|
||||
sk,
|
||||
atomic_read(&sk->sk_refcnt),
|
||||
sk_rmem_alloc_get(sk),
|
||||
sk_wmem_alloc_get(sk),
|
||||
from_kuid(seq_user_ns(seq), sock_i_uid(sk)),
|
||||
sock_i_ino(sk),
|
||||
bt->parent? sock_i_ino(bt->parent): 0LU);
|
||||
|
||||
if (l->custom_seq_show) {
|
||||
seq_putc(seq, ' ');
|
||||
l->custom_seq_show(seq, v);
|
||||
}
|
||||
|
||||
seq_putc(seq, '\n');
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations bt_seq_ops = {
|
||||
.start = bt_seq_start,
|
||||
.next = bt_seq_next,
|
||||
.stop = bt_seq_stop,
|
||||
.show = bt_seq_show,
|
||||
};
|
||||
|
||||
static int bt_seq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct bt_sock_list *sk_list;
|
||||
struct bt_seq_state *s;
|
||||
|
||||
sk_list = PDE_DATA(inode);
|
||||
s = __seq_open_private(file, &bt_seq_ops,
|
||||
sizeof(struct bt_seq_state));
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
s->l = sk_list;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations bt_fops = {
|
||||
.open = bt_seq_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release_private
|
||||
};
|
||||
|
||||
int bt_procfs_init(struct net *net, const char *name,
|
||||
struct bt_sock_list* sk_list,
|
||||
int (* seq_show)(struct seq_file *, void *))
|
||||
{
|
||||
sk_list->custom_seq_show = seq_show;
|
||||
|
||||
if (!proc_create_data(name, 0, net->proc_net, &bt_fops, sk_list))
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bt_procfs_cleanup(struct net *net, const char *name)
|
||||
{
|
||||
remove_proc_entry(name, net->proc_net);
|
||||
}
|
||||
#else
|
||||
int bt_procfs_init(struct net *net, const char *name,
|
||||
struct bt_sock_list* sk_list,
|
||||
int (* seq_show)(struct seq_file *, void *))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bt_procfs_cleanup(struct net *net, const char *name)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
EXPORT_SYMBOL(bt_procfs_init);
|
||||
EXPORT_SYMBOL(bt_procfs_cleanup);
|
||||
|
||||
static struct net_proto_family bt_sock_family_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.family = PF_BLUETOOTH,
|
||||
.create = bt_sock_create,
|
||||
};
|
||||
|
||||
struct dentry *bt_debugfs;
|
||||
EXPORT_SYMBOL_GPL(bt_debugfs);
|
||||
|
||||
static int __init bt_init(void)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
int err;
|
||||
|
||||
BUILD_BUG_ON(sizeof(struct bt_skb_cb) > sizeof(skb->cb));
|
||||
|
||||
BT_INFO("Core ver %s", VERSION);
|
||||
|
||||
bt_debugfs = debugfs_create_dir("bluetooth", NULL);
|
||||
|
||||
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");
|
||||
|
||||
err = hci_sock_init();
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = l2cap_init();
|
||||
if (err < 0)
|
||||
goto sock_err;
|
||||
|
||||
err = sco_init();
|
||||
if (err < 0) {
|
||||
l2cap_exit();
|
||||
goto sock_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
sock_err:
|
||||
hci_sock_cleanup();
|
||||
|
||||
error:
|
||||
sock_unregister(PF_BLUETOOTH);
|
||||
bt_sysfs_cleanup();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit bt_exit(void)
|
||||
{
|
||||
sco_exit();
|
||||
|
||||
l2cap_exit();
|
||||
|
||||
hci_sock_cleanup();
|
||||
|
||||
sock_unregister(PF_BLUETOOTH);
|
||||
|
||||
bt_sysfs_cleanup();
|
||||
|
||||
debugfs_remove_recursive(bt_debugfs);
|
||||
}
|
||||
|
||||
subsys_initcall(bt_init);
|
||||
module_exit(bt_exit);
|
||||
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth Core ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_NETPROTO(PF_BLUETOOTH);
|
467
net/bluetooth/amp.c
Normal file
467
net/bluetooth/amp.c
Normal file
|
@ -0,0 +1,467 @@
|
|||
/*
|
||||
Copyright (c) 2011,2012 Intel Corp.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 and
|
||||
only 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.
|
||||
*/
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/hci.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
#include <crypto/hash.h>
|
||||
|
||||
#include "a2mp.h"
|
||||
#include "amp.h"
|
||||
|
||||
/* Remote AMP Controllers interface */
|
||||
void amp_ctrl_get(struct amp_ctrl *ctrl)
|
||||
{
|
||||
BT_DBG("ctrl %p orig refcnt %d", ctrl,
|
||||
atomic_read(&ctrl->kref.refcount));
|
||||
|
||||
kref_get(&ctrl->kref);
|
||||
}
|
||||
|
||||
static void amp_ctrl_destroy(struct kref *kref)
|
||||
{
|
||||
struct amp_ctrl *ctrl = container_of(kref, struct amp_ctrl, kref);
|
||||
|
||||
BT_DBG("ctrl %p", ctrl);
|
||||
|
||||
kfree(ctrl->assoc);
|
||||
kfree(ctrl);
|
||||
}
|
||||
|
||||
int amp_ctrl_put(struct amp_ctrl *ctrl)
|
||||
{
|
||||
BT_DBG("ctrl %p orig refcnt %d", ctrl,
|
||||
atomic_read(&ctrl->kref.refcount));
|
||||
|
||||
return kref_put(&ctrl->kref, &_ctrl_destroy);
|
||||
}
|
||||
|
||||
struct amp_ctrl *amp_ctrl_add(struct amp_mgr *mgr, u8 id)
|
||||
{
|
||||
struct amp_ctrl *ctrl;
|
||||
|
||||
ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
|
||||
if (!ctrl)
|
||||
return NULL;
|
||||
|
||||
kref_init(&ctrl->kref);
|
||||
ctrl->id = id;
|
||||
|
||||
mutex_lock(&mgr->amp_ctrls_lock);
|
||||
list_add(&ctrl->list, &mgr->amp_ctrls);
|
||||
mutex_unlock(&mgr->amp_ctrls_lock);
|
||||
|
||||
BT_DBG("mgr %p ctrl %p", mgr, ctrl);
|
||||
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
void amp_ctrl_list_flush(struct amp_mgr *mgr)
|
||||
{
|
||||
struct amp_ctrl *ctrl, *n;
|
||||
|
||||
BT_DBG("mgr %p", mgr);
|
||||
|
||||
mutex_lock(&mgr->amp_ctrls_lock);
|
||||
list_for_each_entry_safe(ctrl, n, &mgr->amp_ctrls, list) {
|
||||
list_del(&ctrl->list);
|
||||
amp_ctrl_put(ctrl);
|
||||
}
|
||||
mutex_unlock(&mgr->amp_ctrls_lock);
|
||||
}
|
||||
|
||||
struct amp_ctrl *amp_ctrl_lookup(struct amp_mgr *mgr, u8 id)
|
||||
{
|
||||
struct amp_ctrl *ctrl;
|
||||
|
||||
BT_DBG("mgr %p id %d", mgr, id);
|
||||
|
||||
mutex_lock(&mgr->amp_ctrls_lock);
|
||||
list_for_each_entry(ctrl, &mgr->amp_ctrls, list) {
|
||||
if (ctrl->id == id) {
|
||||
amp_ctrl_get(ctrl);
|
||||
mutex_unlock(&mgr->amp_ctrls_lock);
|
||||
return ctrl;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&mgr->amp_ctrls_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Physical Link interface */
|
||||
static u8 __next_handle(struct amp_mgr *mgr)
|
||||
{
|
||||
if (++mgr->handle == 0)
|
||||
mgr->handle = 1;
|
||||
|
||||
return mgr->handle;
|
||||
}
|
||||
|
||||
struct hci_conn *phylink_add(struct hci_dev *hdev, struct amp_mgr *mgr,
|
||||
u8 remote_id, bool out)
|
||||
{
|
||||
bdaddr_t *dst = &mgr->l2cap_conn->hcon->dst;
|
||||
struct hci_conn *hcon;
|
||||
u8 role = out ? HCI_ROLE_MASTER : HCI_ROLE_SLAVE;
|
||||
|
||||
hcon = hci_conn_add(hdev, AMP_LINK, dst, role);
|
||||
if (!hcon)
|
||||
return NULL;
|
||||
|
||||
BT_DBG("hcon %p dst %pMR", hcon, dst);
|
||||
|
||||
hcon->state = BT_CONNECT;
|
||||
hcon->attempt++;
|
||||
hcon->handle = __next_handle(mgr);
|
||||
hcon->remote_id = remote_id;
|
||||
hcon->amp_mgr = amp_mgr_get(mgr);
|
||||
|
||||
return hcon;
|
||||
}
|
||||
|
||||
/* AMP crypto key generation interface */
|
||||
static int hmac_sha256(u8 *key, u8 ksize, char *plaintext, u8 psize, u8 *output)
|
||||
{
|
||||
struct crypto_shash *tfm;
|
||||
int ret;
|
||||
|
||||
if (!ksize)
|
||||
return -EINVAL;
|
||||
|
||||
tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
|
||||
if (IS_ERR(tfm)) {
|
||||
BT_DBG("crypto_alloc_ahash failed: err %ld", PTR_ERR(tfm));
|
||||
return PTR_ERR(tfm);
|
||||
}
|
||||
|
||||
ret = crypto_shash_setkey(tfm, key, ksize);
|
||||
if (ret) {
|
||||
BT_DBG("crypto_ahash_setkey failed: err %d", ret);
|
||||
} else {
|
||||
char desc[sizeof(struct shash_desc) +
|
||||
crypto_shash_descsize(tfm)] CRYPTO_MINALIGN_ATTR;
|
||||
struct shash_desc *shash = (struct shash_desc *)desc;
|
||||
|
||||
shash->tfm = tfm;
|
||||
shash->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
|
||||
|
||||
ret = crypto_shash_digest(shash, plaintext, psize,
|
||||
output);
|
||||
}
|
||||
|
||||
crypto_free_shash(tfm);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int phylink_gen_key(struct hci_conn *conn, u8 *data, u8 *len, u8 *type)
|
||||
{
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
struct link_key *key;
|
||||
u8 keybuf[HCI_AMP_LINK_KEY_SIZE];
|
||||
u8 gamp_key[HCI_AMP_LINK_KEY_SIZE];
|
||||
int err;
|
||||
|
||||
if (!hci_conn_check_link_mode(conn))
|
||||
return -EACCES;
|
||||
|
||||
BT_DBG("conn %p key_type %d", conn, conn->key_type);
|
||||
|
||||
/* Legacy key */
|
||||
if (conn->key_type < 3) {
|
||||
BT_ERR("Legacy key type %d", conn->key_type);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
*type = conn->key_type;
|
||||
*len = HCI_AMP_LINK_KEY_SIZE;
|
||||
|
||||
key = hci_find_link_key(hdev, &conn->dst);
|
||||
if (!key) {
|
||||
BT_DBG("No Link key for conn %p dst %pMR", conn, &conn->dst);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
/* BR/EDR Link Key concatenated together with itself */
|
||||
memcpy(&keybuf[0], key->val, HCI_LINK_KEY_SIZE);
|
||||
memcpy(&keybuf[HCI_LINK_KEY_SIZE], key->val, HCI_LINK_KEY_SIZE);
|
||||
|
||||
/* Derive Generic AMP Link Key (gamp) */
|
||||
err = hmac_sha256(keybuf, HCI_AMP_LINK_KEY_SIZE, "gamp", 4, gamp_key);
|
||||
if (err) {
|
||||
BT_ERR("Could not derive Generic AMP Key: err %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (conn->key_type == HCI_LK_DEBUG_COMBINATION) {
|
||||
BT_DBG("Use Generic AMP Key (gamp)");
|
||||
memcpy(data, gamp_key, HCI_AMP_LINK_KEY_SIZE);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Derive Dedicated AMP Link Key: "802b" is 802.11 PAL keyID */
|
||||
return hmac_sha256(gamp_key, HCI_AMP_LINK_KEY_SIZE, "802b", 4, data);
|
||||
}
|
||||
|
||||
void amp_read_loc_assoc_frag(struct hci_dev *hdev, u8 phy_handle)
|
||||
{
|
||||
struct hci_cp_read_local_amp_assoc cp;
|
||||
struct amp_assoc *loc_assoc = &hdev->loc_assoc;
|
||||
|
||||
BT_DBG("%s handle %d", hdev->name, phy_handle);
|
||||
|
||||
cp.phy_handle = phy_handle;
|
||||
cp.max_len = cpu_to_le16(hdev->amp_assoc_size);
|
||||
cp.len_so_far = cpu_to_le16(loc_assoc->offset);
|
||||
|
||||
hci_send_cmd(hdev, HCI_OP_READ_LOCAL_AMP_ASSOC, sizeof(cp), &cp);
|
||||
}
|
||||
|
||||
void amp_read_loc_assoc(struct hci_dev *hdev, struct amp_mgr *mgr)
|
||||
{
|
||||
struct hci_cp_read_local_amp_assoc cp;
|
||||
|
||||
memset(&hdev->loc_assoc, 0, sizeof(struct amp_assoc));
|
||||
memset(&cp, 0, sizeof(cp));
|
||||
|
||||
cp.max_len = cpu_to_le16(hdev->amp_assoc_size);
|
||||
|
||||
set_bit(READ_LOC_AMP_ASSOC, &mgr->state);
|
||||
hci_send_cmd(hdev, HCI_OP_READ_LOCAL_AMP_ASSOC, sizeof(cp), &cp);
|
||||
}
|
||||
|
||||
void amp_read_loc_assoc_final_data(struct hci_dev *hdev,
|
||||
struct hci_conn *hcon)
|
||||
{
|
||||
struct hci_cp_read_local_amp_assoc cp;
|
||||
struct amp_mgr *mgr = hcon->amp_mgr;
|
||||
|
||||
cp.phy_handle = hcon->handle;
|
||||
cp.len_so_far = cpu_to_le16(0);
|
||||
cp.max_len = cpu_to_le16(hdev->amp_assoc_size);
|
||||
|
||||
set_bit(READ_LOC_AMP_ASSOC_FINAL, &mgr->state);
|
||||
|
||||
/* Read Local AMP Assoc final link information data */
|
||||
hci_send_cmd(hdev, HCI_OP_READ_LOCAL_AMP_ASSOC, sizeof(cp), &cp);
|
||||
}
|
||||
|
||||
/* Write AMP Assoc data fragments, returns true with last fragment written*/
|
||||
static bool amp_write_rem_assoc_frag(struct hci_dev *hdev,
|
||||
struct hci_conn *hcon)
|
||||
{
|
||||
struct hci_cp_write_remote_amp_assoc *cp;
|
||||
struct amp_mgr *mgr = hcon->amp_mgr;
|
||||
struct amp_ctrl *ctrl;
|
||||
u16 frag_len, len;
|
||||
|
||||
ctrl = amp_ctrl_lookup(mgr, hcon->remote_id);
|
||||
if (!ctrl)
|
||||
return false;
|
||||
|
||||
if (!ctrl->assoc_rem_len) {
|
||||
BT_DBG("all fragments are written");
|
||||
ctrl->assoc_rem_len = ctrl->assoc_len;
|
||||
ctrl->assoc_len_so_far = 0;
|
||||
|
||||
amp_ctrl_put(ctrl);
|
||||
return true;
|
||||
}
|
||||
|
||||
frag_len = min_t(u16, 248, ctrl->assoc_rem_len);
|
||||
len = frag_len + sizeof(*cp);
|
||||
|
||||
cp = kzalloc(len, GFP_KERNEL);
|
||||
if (!cp) {
|
||||
amp_ctrl_put(ctrl);
|
||||
return false;
|
||||
}
|
||||
|
||||
BT_DBG("hcon %p ctrl %p frag_len %u assoc_len %u rem_len %u",
|
||||
hcon, ctrl, frag_len, ctrl->assoc_len, ctrl->assoc_rem_len);
|
||||
|
||||
cp->phy_handle = hcon->handle;
|
||||
cp->len_so_far = cpu_to_le16(ctrl->assoc_len_so_far);
|
||||
cp->rem_len = cpu_to_le16(ctrl->assoc_rem_len);
|
||||
memcpy(cp->frag, ctrl->assoc, frag_len);
|
||||
|
||||
ctrl->assoc_len_so_far += frag_len;
|
||||
ctrl->assoc_rem_len -= frag_len;
|
||||
|
||||
amp_ctrl_put(ctrl);
|
||||
|
||||
hci_send_cmd(hdev, HCI_OP_WRITE_REMOTE_AMP_ASSOC, len, cp);
|
||||
|
||||
kfree(cp);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void amp_write_rem_assoc_continue(struct hci_dev *hdev, u8 handle)
|
||||
{
|
||||
struct hci_conn *hcon;
|
||||
|
||||
BT_DBG("%s phy handle 0x%2.2x", hdev->name, handle);
|
||||
|
||||
hcon = hci_conn_hash_lookup_handle(hdev, handle);
|
||||
if (!hcon)
|
||||
return;
|
||||
|
||||
/* Send A2MP create phylink rsp when all fragments are written */
|
||||
if (amp_write_rem_assoc_frag(hdev, hcon))
|
||||
a2mp_send_create_phy_link_rsp(hdev, 0);
|
||||
}
|
||||
|
||||
void amp_write_remote_assoc(struct hci_dev *hdev, u8 handle)
|
||||
{
|
||||
struct hci_conn *hcon;
|
||||
|
||||
BT_DBG("%s phy handle 0x%2.2x", hdev->name, handle);
|
||||
|
||||
hcon = hci_conn_hash_lookup_handle(hdev, handle);
|
||||
if (!hcon)
|
||||
return;
|
||||
|
||||
BT_DBG("%s phy handle 0x%2.2x hcon %p", hdev->name, handle, hcon);
|
||||
|
||||
amp_write_rem_assoc_frag(hdev, hcon);
|
||||
}
|
||||
|
||||
void amp_create_phylink(struct hci_dev *hdev, struct amp_mgr *mgr,
|
||||
struct hci_conn *hcon)
|
||||
{
|
||||
struct hci_cp_create_phy_link cp;
|
||||
|
||||
cp.phy_handle = hcon->handle;
|
||||
|
||||
BT_DBG("%s hcon %p phy handle 0x%2.2x", hdev->name, hcon,
|
||||
hcon->handle);
|
||||
|
||||
if (phylink_gen_key(mgr->l2cap_conn->hcon, cp.key, &cp.key_len,
|
||||
&cp.key_type)) {
|
||||
BT_DBG("Cannot create link key");
|
||||
return;
|
||||
}
|
||||
|
||||
hci_send_cmd(hdev, HCI_OP_CREATE_PHY_LINK, sizeof(cp), &cp);
|
||||
}
|
||||
|
||||
void amp_accept_phylink(struct hci_dev *hdev, struct amp_mgr *mgr,
|
||||
struct hci_conn *hcon)
|
||||
{
|
||||
struct hci_cp_accept_phy_link cp;
|
||||
|
||||
cp.phy_handle = hcon->handle;
|
||||
|
||||
BT_DBG("%s hcon %p phy handle 0x%2.2x", hdev->name, hcon,
|
||||
hcon->handle);
|
||||
|
||||
if (phylink_gen_key(mgr->l2cap_conn->hcon, cp.key, &cp.key_len,
|
||||
&cp.key_type)) {
|
||||
BT_DBG("Cannot create link key");
|
||||
return;
|
||||
}
|
||||
|
||||
hci_send_cmd(hdev, HCI_OP_ACCEPT_PHY_LINK, sizeof(cp), &cp);
|
||||
}
|
||||
|
||||
void amp_physical_cfm(struct hci_conn *bredr_hcon, struct hci_conn *hs_hcon)
|
||||
{
|
||||
struct hci_dev *bredr_hdev = hci_dev_hold(bredr_hcon->hdev);
|
||||
struct amp_mgr *mgr = hs_hcon->amp_mgr;
|
||||
struct l2cap_chan *bredr_chan;
|
||||
|
||||
BT_DBG("bredr_hcon %p hs_hcon %p mgr %p", bredr_hcon, hs_hcon, mgr);
|
||||
|
||||
if (!bredr_hdev || !mgr || !mgr->bredr_chan)
|
||||
return;
|
||||
|
||||
bredr_chan = mgr->bredr_chan;
|
||||
|
||||
l2cap_chan_lock(bredr_chan);
|
||||
|
||||
set_bit(FLAG_EFS_ENABLE, &bredr_chan->flags);
|
||||
bredr_chan->remote_amp_id = hs_hcon->remote_id;
|
||||
bredr_chan->local_amp_id = hs_hcon->hdev->id;
|
||||
bredr_chan->hs_hcon = hs_hcon;
|
||||
bredr_chan->conn->mtu = hs_hcon->hdev->block_mtu;
|
||||
|
||||
__l2cap_physical_cfm(bredr_chan, 0);
|
||||
|
||||
l2cap_chan_unlock(bredr_chan);
|
||||
|
||||
hci_dev_put(bredr_hdev);
|
||||
}
|
||||
|
||||
void amp_create_logical_link(struct l2cap_chan *chan)
|
||||
{
|
||||
struct hci_conn *hs_hcon = chan->hs_hcon;
|
||||
struct hci_cp_create_accept_logical_link cp;
|
||||
struct hci_dev *hdev;
|
||||
|
||||
BT_DBG("chan %p hs_hcon %p dst %pMR", chan, hs_hcon,
|
||||
&chan->conn->hcon->dst);
|
||||
|
||||
if (!hs_hcon)
|
||||
return;
|
||||
|
||||
hdev = hci_dev_hold(chan->hs_hcon->hdev);
|
||||
if (!hdev)
|
||||
return;
|
||||
|
||||
cp.phy_handle = hs_hcon->handle;
|
||||
|
||||
cp.tx_flow_spec.id = chan->local_id;
|
||||
cp.tx_flow_spec.stype = chan->local_stype;
|
||||
cp.tx_flow_spec.msdu = cpu_to_le16(chan->local_msdu);
|
||||
cp.tx_flow_spec.sdu_itime = cpu_to_le32(chan->local_sdu_itime);
|
||||
cp.tx_flow_spec.acc_lat = cpu_to_le32(chan->local_acc_lat);
|
||||
cp.tx_flow_spec.flush_to = cpu_to_le32(chan->local_flush_to);
|
||||
|
||||
cp.rx_flow_spec.id = chan->remote_id;
|
||||
cp.rx_flow_spec.stype = chan->remote_stype;
|
||||
cp.rx_flow_spec.msdu = cpu_to_le16(chan->remote_msdu);
|
||||
cp.rx_flow_spec.sdu_itime = cpu_to_le32(chan->remote_sdu_itime);
|
||||
cp.rx_flow_spec.acc_lat = cpu_to_le32(chan->remote_acc_lat);
|
||||
cp.rx_flow_spec.flush_to = cpu_to_le32(chan->remote_flush_to);
|
||||
|
||||
if (hs_hcon->out)
|
||||
hci_send_cmd(hdev, HCI_OP_CREATE_LOGICAL_LINK, sizeof(cp),
|
||||
&cp);
|
||||
else
|
||||
hci_send_cmd(hdev, HCI_OP_ACCEPT_LOGICAL_LINK, sizeof(cp),
|
||||
&cp);
|
||||
|
||||
hci_dev_put(hdev);
|
||||
}
|
||||
|
||||
void amp_disconnect_logical_link(struct hci_chan *hchan)
|
||||
{
|
||||
struct hci_conn *hcon = hchan->conn;
|
||||
struct hci_cp_disconn_logical_link cp;
|
||||
|
||||
if (hcon->state != BT_CONNECTED) {
|
||||
BT_DBG("hchan %p not connected", hchan);
|
||||
return;
|
||||
}
|
||||
|
||||
cp.log_handle = cpu_to_le16(hchan->handle);
|
||||
hci_send_cmd(hcon->hdev, HCI_OP_DISCONN_LOGICAL_LINK, sizeof(cp), &cp);
|
||||
}
|
||||
|
||||
void amp_destroy_logical_link(struct hci_chan *hchan, u8 reason)
|
||||
{
|
||||
BT_DBG("hchan %p", hchan);
|
||||
|
||||
hci_chan_del(hchan);
|
||||
}
|
54
net/bluetooth/amp.h
Normal file
54
net/bluetooth/amp.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright (c) 2011,2012 Intel Corp.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 and
|
||||
only 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.
|
||||
*/
|
||||
|
||||
#ifndef __AMP_H
|
||||
#define __AMP_H
|
||||
|
||||
struct amp_ctrl {
|
||||
struct list_head list;
|
||||
struct kref kref;
|
||||
__u8 id;
|
||||
__u16 assoc_len_so_far;
|
||||
__u16 assoc_rem_len;
|
||||
__u16 assoc_len;
|
||||
__u8 *assoc;
|
||||
};
|
||||
|
||||
int amp_ctrl_put(struct amp_ctrl *ctrl);
|
||||
void amp_ctrl_get(struct amp_ctrl *ctrl);
|
||||
struct amp_ctrl *amp_ctrl_add(struct amp_mgr *mgr, u8 id);
|
||||
struct amp_ctrl *amp_ctrl_lookup(struct amp_mgr *mgr, u8 id);
|
||||
void amp_ctrl_list_flush(struct amp_mgr *mgr);
|
||||
|
||||
struct hci_conn *phylink_add(struct hci_dev *hdev, struct amp_mgr *mgr,
|
||||
u8 remote_id, bool out);
|
||||
|
||||
int phylink_gen_key(struct hci_conn *hcon, u8 *data, u8 *len, u8 *type);
|
||||
|
||||
void amp_read_loc_info(struct hci_dev *hdev, struct amp_mgr *mgr);
|
||||
void amp_read_loc_assoc_frag(struct hci_dev *hdev, u8 phy_handle);
|
||||
void amp_read_loc_assoc(struct hci_dev *hdev, struct amp_mgr *mgr);
|
||||
void amp_read_loc_assoc_final_data(struct hci_dev *hdev,
|
||||
struct hci_conn *hcon);
|
||||
void amp_create_phylink(struct hci_dev *hdev, struct amp_mgr *mgr,
|
||||
struct hci_conn *hcon);
|
||||
void amp_accept_phylink(struct hci_dev *hdev, struct amp_mgr *mgr,
|
||||
struct hci_conn *hcon);
|
||||
void amp_write_remote_assoc(struct hci_dev *hdev, u8 handle);
|
||||
void amp_write_rem_assoc_continue(struct hci_dev *hdev, u8 handle);
|
||||
void amp_physical_cfm(struct hci_conn *bredr_hcon, struct hci_conn *hs_hcon);
|
||||
void amp_create_logical_link(struct l2cap_chan *chan);
|
||||
void amp_disconnect_logical_link(struct hci_chan *hchan);
|
||||
void amp_destroy_logical_link(struct hci_chan *hchan, u8 reason);
|
||||
|
||||
#endif /* __AMP_H */
|
24
net/bluetooth/bnep/Kconfig
Normal file
24
net/bluetooth/bnep/Kconfig
Normal file
|
@ -0,0 +1,24 @@
|
|||
config BT_BNEP
|
||||
tristate "BNEP protocol support"
|
||||
depends on BT
|
||||
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.
|
||||
|
7
net/bluetooth/bnep/Makefile
Normal file
7
net/bluetooth/bnep/Makefile
Normal 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
|
179
net/bluetooth/bnep/bnep.h
Normal file
179
net/bluetooth/bnep/bnep.h
Normal file
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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];
|
||||
} __packed;
|
||||
|
||||
struct bnep_set_filter_req {
|
||||
__u8 type;
|
||||
__u8 ctrl;
|
||||
__be16 len;
|
||||
__u8 list[0];
|
||||
} __packed;
|
||||
|
||||
struct bnep_control_rsp {
|
||||
__u8 type;
|
||||
__u8 ctrl;
|
||||
__be16 resp;
|
||||
} __packed;
|
||||
|
||||
struct bnep_ext_hdr {
|
||||
__u8 type;
|
||||
__u8 len;
|
||||
__u8 data[0];
|
||||
} __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 terminate;
|
||||
struct task_struct *task;
|
||||
|
||||
struct ethhdr eh;
|
||||
struct msghdr msg;
|
||||
|
||||
struct bnep_proto_filter proto_filter[BNEP_MAX_PROTO_FILTERS];
|
||||
unsigned long long mc_filter;
|
||||
|
||||
struct socket *sock;
|
||||
struct net_device *dev;
|
||||
};
|
||||
|
||||
void bnep_net_setup(struct net_device *dev);
|
||||
int bnep_sock_init(void);
|
||||
void bnep_sock_cleanup(void);
|
||||
|
||||
static inline int bnep_mc_hash(__u8 *addr)
|
||||
{
|
||||
return crc32_be(~0, addr, ETH_ALEN) >> 26;
|
||||
}
|
||||
|
||||
#endif
|
723
net/bluetooth/bnep/core.c
Normal file
723
net/bluetooth/bnep/core.c
Normal file
|
@ -0,0 +1,723 @@
|
|||
/*
|
||||
BNEP implementation for Linux Bluetooth stack (BlueZ).
|
||||
Copyright (C) 2001-2002 Inventel Systemes
|
||||
Written 2001-2002 by
|
||||
Clé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.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/l2cap.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
|
||||
#include "bnep.h"
|
||||
|
||||
#define VERSION "1.3"
|
||||
|
||||
static bool compress_src = true;
|
||||
static bool compress_dst = true;
|
||||
|
||||
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;
|
||||
|
||||
BT_DBG("");
|
||||
|
||||
list_for_each_entry(s, &bnep_session_list, list)
|
||||
if (ether_addr_equal(dst, s->eh.h_source))
|
||||
return s;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void __bnep_link_session(struct bnep_session *s)
|
||||
{
|
||||
list_add(&s->list, &bnep_session_list);
|
||||
}
|
||||
|
||||
static void __bnep_unlink_session(struct bnep_session *s)
|
||||
{
|
||||
list_del(&s->list);
|
||||
}
|
||||
|
||||
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 = get_unaligned_be16(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 = get_unaligned_be16(data++);
|
||||
f[i].end = get_unaligned_be16(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 = 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) {
|
||||
int i;
|
||||
|
||||
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 %pMR -> %pMR", a1, a2);
|
||||
|
||||
/* 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) {
|
||||
/* Increment a1 */
|
||||
i = 5;
|
||||
while (i >= 0 && ++a1[i--] == 0)
|
||||
;
|
||||
|
||||
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_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;
|
||||
|
||||
case BNEP_SETUP_CONN_REQ:
|
||||
err = bnep_send_rsp(s, BNEP_SETUP_CONN_RSP, BNEP_CONN_NOT_ALLOWED);
|
||||
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 */
|
||||
};
|
||||
|
||||
static 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->stats.rx_bytes += skb->len;
|
||||
|
||||
type = *(u8 *) skb->data;
|
||||
skb_pull(skb, 1);
|
||||
|
||||
if ((type & BNEP_TYPE_MASK) >= sizeof(__bnep_rx_hlen))
|
||||
goto badframe;
|
||||
|
||||
if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) {
|
||||
bnep_rx_control(s, skb->data, skb->len);
|
||||
kfree_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
skb_reset_mac_header(skb);
|
||||
|
||||
/* 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) == ETH_P_8021Q) {
|
||||
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) {
|
||||
dev->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_header(skb), 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_header(skb),
|
||||
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_header(skb),
|
||||
ETH_ALEN * 2);
|
||||
put_unaligned(s->eh.h_proto, (__be16 *) __skb_put(nskb, 2));
|
||||
break;
|
||||
}
|
||||
|
||||
skb_copy_from_linear_data(skb, __skb_put(nskb, skb->len), skb->len);
|
||||
kfree_skb(skb);
|
||||
|
||||
dev->stats.rx_packets++;
|
||||
nskb->ip_summed = CHECKSUM_NONE;
|
||||
nskb->protocol = eth_type_trans(nskb, dev);
|
||||
netif_rx_ni(nskb);
|
||||
return 0;
|
||||
|
||||
badframe:
|
||||
dev->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 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 (compress_src && ether_addr_equal(eh->h_dest, s->eh.h_source))
|
||||
type |= 0x01;
|
||||
|
||||
if (compress_dst && ether_addr_equal(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->dev->stats.tx_bytes += len;
|
||||
s->dev->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("");
|
||||
|
||||
set_user_nice(current, -15);
|
||||
|
||||
init_waitqueue_entry(&wait, current);
|
||||
add_wait_queue(sk_sleep(sk), &wait);
|
||||
while (1) {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
if (atomic_read(&s->terminate))
|
||||
break;
|
||||
/* RX */
|
||||
while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
|
||||
skb_orphan(skb);
|
||||
if (!skb_linearize(skb))
|
||||
bnep_rx_frame(s, skb);
|
||||
else
|
||||
kfree_skb(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_sleep(sk), &wait);
|
||||
|
||||
/* Cleanup session */
|
||||
down_write(&bnep_session_sem);
|
||||
|
||||
/* Delete network device */
|
||||
unregister_netdev(dev);
|
||||
|
||||
/* Wakeup user-space polling for socket errors */
|
||||
s->sock->sk->sk_err = EUNATCH;
|
||||
|
||||
wake_up_interruptible(sk_sleep(s->sock->sk));
|
||||
|
||||
/* Release the socket */
|
||||
fput(s->sock->file);
|
||||
|
||||
__bnep_unlink_session(s);
|
||||
|
||||
up_write(&bnep_session_sem);
|
||||
free_netdev(dev);
|
||||
module_put_and_exit(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct device *bnep_get_device(struct bnep_session *session)
|
||||
{
|
||||
struct hci_conn *conn;
|
||||
|
||||
conn = l2cap_pi(session->sock->sk)->chan->conn->hcon;
|
||||
if (!conn)
|
||||
return NULL;
|
||||
|
||||
return &conn->dev;
|
||||
}
|
||||
|
||||
static struct device_type bnep_type = {
|
||||
.name = "bluetooth",
|
||||
};
|
||||
|
||||
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, &l2cap_pi(sock->sk)->chan->dst);
|
||||
baswap((void *) src, &l2cap_pi(sock->sk)->chan->src);
|
||||
|
||||
/* session struct allocated as private part of net_device */
|
||||
dev = alloc_netdev(sizeof(struct bnep_session),
|
||||
(*req->device) ? req->device : "bnep%d",
|
||||
NET_NAME_UNKNOWN,
|
||||
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 = netdev_priv(dev);
|
||||
|
||||
/* 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));
|
||||
SET_NETDEV_DEVTYPE(dev, &bnep_type);
|
||||
|
||||
err = register_netdev(dev);
|
||||
if (err)
|
||||
goto failed;
|
||||
|
||||
__bnep_link_session(s);
|
||||
|
||||
__module_get(THIS_MODULE);
|
||||
s->task = kthread_run(bnep_session, s, "kbnepd %s", dev->name);
|
||||
if (IS_ERR(s->task)) {
|
||||
/* Session thread start failed, gotta cleanup. */
|
||||
module_put(THIS_MODULE);
|
||||
unregister_netdev(dev);
|
||||
__bnep_unlink_session(s);
|
||||
err = PTR_ERR(s->task);
|
||||
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) {
|
||||
atomic_inc(&s->terminate);
|
||||
wake_up_process(s->task);
|
||||
} else
|
||||
err = -ENOENT;
|
||||
|
||||
up_read(&bnep_session_sem);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __bnep_copy_ci(struct bnep_conninfo *ci, struct bnep_session *s)
|
||||
{
|
||||
memset(ci, 0, sizeof(*ci));
|
||||
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 bnep_session *s;
|
||||
int err = 0, n = 0;
|
||||
|
||||
down_read(&bnep_session_sem);
|
||||
|
||||
list_for_each_entry(s, &bnep_session_list, list) {
|
||||
struct bnep_conninfo ci;
|
||||
|
||||
__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] = "";
|
||||
|
||||
#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_param(compress_src, bool, 0644);
|
||||
MODULE_PARM_DESC(compress_src, "Compress sources headers");
|
||||
|
||||
module_param(compress_dst, bool, 0644);
|
||||
MODULE_PARM_DESC(compress_dst, "Compress destination headers");
|
||||
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth BNEP ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("bt-proto-4");
|
229
net/bluetooth/bnep/netdev.c
Normal file
229
net/bluetooth/bnep/netdev.c
Normal file
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
BNEP implementation for Linux Bluetooth stack (BlueZ).
|
||||
Copyright (C) 2001-2002 Inventel Systemes
|
||||
Written 2001-2002 by
|
||||
Clé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.
|
||||
*/
|
||||
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
#include <net/bluetooth/l2cap.h>
|
||||
|
||||
#include "bnep.h"
|
||||
|
||||
#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 void bnep_net_set_mc_list(struct net_device *dev)
|
||||
{
|
||||
#ifdef CONFIG_BT_BNEP_MC_FILTER
|
||||
struct bnep_session *s = netdev_priv(dev);
|
||||
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, netdev_mc_count(dev));
|
||||
|
||||
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 netdev_hw_addr *ha;
|
||||
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. */
|
||||
|
||||
i = 0;
|
||||
netdev_for_each_mc_addr(ha, dev) {
|
||||
if (i == BNEP_MAX_MULTICAST_FILTERS)
|
||||
break;
|
||||
memcpy(__skb_put(skb, ETH_ALEN), ha->addr, ETH_ALEN);
|
||||
memcpy(__skb_put(skb, ETH_ALEN), ha->addr, ETH_ALEN);
|
||||
|
||||
i++;
|
||||
}
|
||||
r->len = htons(skb->len - len);
|
||||
}
|
||||
|
||||
skb_queue_tail(&sk->sk_write_queue, skb);
|
||||
wake_up_interruptible(sk_sleep(sk));
|
||||
#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);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BT_BNEP_MC_FILTER
|
||||
static 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 u16 bnep_net_eth_proto(struct sk_buff *skb)
|
||||
{
|
||||
struct ethhdr *eh = (void *) skb->data;
|
||||
u16 proto = ntohs(eh->h_proto);
|
||||
|
||||
if (proto >= ETH_P_802_3_MIN)
|
||||
return proto;
|
||||
|
||||
if (get_unaligned((__be16 *) skb->data) == htons(0xFFFF))
|
||||
return ETH_P_802_3;
|
||||
|
||||
return ETH_P_802_2;
|
||||
}
|
||||
|
||||
static 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 netdev_tx_t bnep_net_xmit(struct sk_buff *skb,
|
||||
struct net_device *dev)
|
||||
{
|
||||
struct bnep_session *s = netdev_priv(dev);
|
||||
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 NETDEV_TX_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BT_BNEP_PROTO_FILTER
|
||||
if (bnep_net_proto_filter(skb, s)) {
|
||||
kfree_skb(skb);
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We cannot send L2CAP packets from here as we are potentially in a bh.
|
||||
* So we have to queue them and wake up session thread which is sleeping
|
||||
* on the sk_sleep(sk).
|
||||
*/
|
||||
dev->trans_start = jiffies;
|
||||
skb_queue_tail(&sk->sk_write_queue, skb);
|
||||
wake_up_interruptible(sk_sleep(sk));
|
||||
|
||||
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 NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static const struct net_device_ops bnep_netdev_ops = {
|
||||
.ndo_open = bnep_net_open,
|
||||
.ndo_stop = bnep_net_close,
|
||||
.ndo_start_xmit = bnep_net_xmit,
|
||||
.ndo_validate_addr = eth_validate_addr,
|
||||
.ndo_set_rx_mode = bnep_net_set_mc_list,
|
||||
.ndo_set_mac_address = bnep_net_set_mac_addr,
|
||||
.ndo_tx_timeout = bnep_net_timeout,
|
||||
.ndo_change_mtu = eth_change_mtu,
|
||||
|
||||
};
|
||||
|
||||
void bnep_net_setup(struct net_device *dev)
|
||||
{
|
||||
|
||||
memset(dev->broadcast, 0xff, ETH_ALEN);
|
||||
dev->addr_len = ETH_ALEN;
|
||||
|
||||
ether_setup(dev);
|
||||
dev->priv_flags &= ~IFF_TX_SKB_SHARING;
|
||||
dev->netdev_ops = &bnep_netdev_ops;
|
||||
|
||||
dev->watchdog_timeo = HZ * 2;
|
||||
}
|
258
net/bluetooth/bnep/sock.c
Normal file
258
net/bluetooth/bnep/sock.c
Normal file
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/file.h>
|
||||
|
||||
#include "bnep.h"
|
||||
|
||||
static struct bt_sock_list bnep_sk_list = {
|
||||
.lock = __RW_LOCK_UNLOCKED(bnep_sk_list.lock)
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
bt_sock_unlink(&bnep_sk_list, sk);
|
||||
|
||||
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 -EPERM;
|
||||
|
||||
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) {
|
||||
sockfd_put(nsock);
|
||||
return -EBADFD;
|
||||
}
|
||||
ca.device[sizeof(ca.device)-1] = 0;
|
||||
|
||||
err = bnep_add_connection(&ca, nsock);
|
||||
if (!err) {
|
||||
if (copy_to_user(argp, &ca, sizeof(ca)))
|
||||
err = -EFAULT;
|
||||
} else
|
||||
sockfd_put(nsock);
|
||||
|
||||
return err;
|
||||
|
||||
case BNEPCONNDEL:
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
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;
|
||||
u32 uci;
|
||||
int err;
|
||||
|
||||
if (get_user(cl.cnum, (u32 __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, (u32 __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 net *net, struct socket *sock, int protocol,
|
||||
int kern)
|
||||
{
|
||||
struct sock *sk;
|
||||
|
||||
BT_DBG("sock %p", sock);
|
||||
|
||||
if (sock->type != SOCK_RAW)
|
||||
return -ESOCKTNOSUPPORT;
|
||||
|
||||
sk = sk_alloc(net, PF_BLUETOOTH, GFP_ATOMIC, &bnep_proto);
|
||||
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;
|
||||
|
||||
bt_sock_link(&bnep_sk_list, sk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const 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) {
|
||||
BT_ERR("Can't register BNEP socket");
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = bt_procfs_init(&init_net, "bnep", &bnep_sk_list, NULL);
|
||||
if (err < 0) {
|
||||
BT_ERR("Failed to create BNEP proc file");
|
||||
bt_sock_unregister(BTPROTO_BNEP);
|
||||
goto error;
|
||||
}
|
||||
|
||||
BT_INFO("BNEP socket layer initialized");
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
proto_unregister(&bnep_proto);
|
||||
return err;
|
||||
}
|
||||
|
||||
void __exit bnep_sock_cleanup(void)
|
||||
{
|
||||
bt_procfs_cleanup(&init_net, "bnep");
|
||||
bt_sock_unregister(BTPROTO_BNEP);
|
||||
proto_unregister(&bnep_proto);
|
||||
}
|
11
net/bluetooth/cmtp/Kconfig
Normal file
11
net/bluetooth/cmtp/Kconfig
Normal file
|
@ -0,0 +1,11 @@
|
|||
config BT_CMTP
|
||||
tristate "CMTP protocol support"
|
||||
depends on BT && 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).
|
||||
|
7
net/bluetooth/cmtp/Makefile
Normal file
7
net/bluetooth/cmtp/Makefile
Normal 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
|
618
net/bluetooth/cmtp/capi.c
Normal file
618
net/bluetooth/cmtp/capi.c
Normal file
|
@ -0,0 +1,618 @@
|
|||
/*
|
||||
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/export.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.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 <linux/kthread.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
#include <linux/isdn/capilli.h>
|
||||
#include <linux/isdn/capicmd.h>
|
||||
#include <linux/isdn/capiutil.h>
|
||||
|
||||
#include "cmtp.h"
|
||||
|
||||
#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);
|
||||
|
||||
wake_up_interruptible(sk_sleep(session->sock->sk));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
skb = alloc_skb(CAPI_MSG_BASELEN + 6 + len, GFP_ATOMIC);
|
||||
if (!skb) {
|
||||
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 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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_down(ctrl);
|
||||
|
||||
atomic_inc(&session->terminate);
|
||||
wake_up_process(session->task);
|
||||
}
|
||||
|
||||
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_proc_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct capi_ctr *ctrl = m->private;
|
||||
struct cmtp_session *session = ctrl->driverdata;
|
||||
struct cmtp_application *app;
|
||||
struct list_head *p, *n;
|
||||
|
||||
seq_printf(m, "%s\n\n", cmtp_procinfo(ctrl));
|
||||
seq_printf(m, "addr %s\n", session->name);
|
||||
seq_printf(m, "ctrl %d\n", session->num);
|
||||
|
||||
list_for_each_safe(p, n, &session->applications) {
|
||||
app = list_entry(p, struct cmtp_application, list);
|
||||
seq_printf(m, "appl %d -> %d\n", app->appl, app->mapping);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmtp_proc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, cmtp_proc_show, PDE_DATA(inode));
|
||||
}
|
||||
|
||||
static const struct file_operations cmtp_proc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = cmtp_proc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
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.proc_fops = &cmtp_proc_fops;
|
||||
|
||||
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);
|
||||
}
|
129
net/bluetooth/cmtp/cmtp.h
Normal file
129
net/bluetooth/cmtp/cmtp.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
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;
|
||||
struct task_struct *task;
|
||||
|
||||
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);
|
||||
|
||||
/* CMTP init defines */
|
||||
int cmtp_init_sockets(void);
|
||||
void cmtp_cleanup_sockets(void);
|
||||
|
||||
#endif /* __CMTP_H */
|
500
net/bluetooth/cmtp/core.c
Normal file
500
net/bluetooth/cmtp/core.c
Normal file
|
@ -0,0 +1,500 @@
|
|||
/*
|
||||
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/freezer.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
#include <linux/isdn/capilli.h>
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/l2cap.h>
|
||||
|
||||
#include "cmtp.h"
|
||||
|
||||
#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;
|
||||
|
||||
BT_DBG("");
|
||||
|
||||
list_for_each_entry(session, &cmtp_session_list, list)
|
||||
if (!bacmp(bdaddr, &session->bdaddr))
|
||||
return session;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void __cmtp_link_session(struct cmtp_session *session)
|
||||
{
|
||||
list_add(&session->list, &cmtp_session_list);
|
||||
}
|
||||
|
||||
static void __cmtp_unlink_session(struct cmtp_session *session)
|
||||
{
|
||||
list_del(&session->list);
|
||||
}
|
||||
|
||||
static void __cmtp_copy_session(struct cmtp_session *session, struct cmtp_conninfo *ci)
|
||||
{
|
||||
memset(ci, 0, sizeof(*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;
|
||||
|
||||
nskb = alloc_skb(size, GFP_ATOMIC);
|
||||
if (!nskb) {
|
||||
BT_ERR("Can't allocate memory for CAPI message");
|
||||
return;
|
||||
}
|
||||
|
||||
if (skb && (skb->len > 0))
|
||||
skb_copy_from_linear_data(skb, skb_put(nskb, skb->len), skb->len);
|
||||
|
||||
memcpy(skb_put(nskb, count), buf, count);
|
||||
|
||||
session->reassembly[id] = nskb;
|
||||
|
||||
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);
|
||||
|
||||
nskb = alloc_skb(session->mtu, GFP_ATOMIC);
|
||||
if (!nskb) {
|
||||
BT_ERR("Can't allocate memory for new frame");
|
||||
return;
|
||||
}
|
||||
|
||||
while ((skb = skb_dequeue(&session->transmit))) {
|
||||
struct cmtp_scb *scb = (void *) skb->cb;
|
||||
|
||||
tail = session->mtu - nskb->len;
|
||||
if (tail < 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);
|
||||
if (scb->id < 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;
|
||||
}
|
||||
|
||||
skb_copy_from_linear_data(skb, skb_put(nskb, size), 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);
|
||||
|
||||
set_user_nice(current, -15);
|
||||
|
||||
init_waitqueue_entry(&wait, current);
|
||||
add_wait_queue(sk_sleep(sk), &wait);
|
||||
while (1) {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
if (atomic_read(&session->terminate))
|
||||
break;
|
||||
if (sk->sk_state != BT_CONNECTED)
|
||||
break;
|
||||
|
||||
while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
|
||||
skb_orphan(skb);
|
||||
if (!skb_linearize(skb))
|
||||
cmtp_recv_frame(session, skb);
|
||||
else
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
cmtp_process_transmit(session);
|
||||
|
||||
schedule();
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(sk_sleep(sk), &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);
|
||||
module_put_and_exit(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock)
|
||||
{
|
||||
struct cmtp_session *session, *s;
|
||||
int i, err;
|
||||
|
||||
BT_DBG("");
|
||||
|
||||
session = kzalloc(sizeof(struct cmtp_session), GFP_KERNEL);
|
||||
if (!session)
|
||||
return -ENOMEM;
|
||||
|
||||
down_write(&cmtp_session_sem);
|
||||
|
||||
s = __cmtp_get_session(&l2cap_pi(sock->sk)->chan->dst);
|
||||
if (s && s->state == BT_CONNECTED) {
|
||||
err = -EEXIST;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
bacpy(&session->bdaddr, &l2cap_pi(sock->sk)->chan->dst);
|
||||
|
||||
session->mtu = min_t(uint, l2cap_pi(sock->sk)->chan->omtu,
|
||||
l2cap_pi(sock->sk)->chan->imtu);
|
||||
|
||||
BT_DBG("mtu %d", session->mtu);
|
||||
|
||||
sprintf(session->name, "%pMR", &session->bdaddr);
|
||||
|
||||
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);
|
||||
|
||||
__module_get(THIS_MODULE);
|
||||
session->task = kthread_run(cmtp_session, session, "kcmtpd_ctr_%d",
|
||||
session->num);
|
||||
if (IS_ERR(session->task)) {
|
||||
module_put(THIS_MODULE);
|
||||
err = PTR_ERR(session->task);
|
||||
goto unlink;
|
||||
}
|
||||
|
||||
if (!(session->flags & (1 << CMTP_LOOPBACK))) {
|
||||
err = cmtp_attach_device(session);
|
||||
if (err < 0) {
|
||||
atomic_inc(&session->terminate);
|
||||
wake_up_process(session->task);
|
||||
up_write(&cmtp_session_sem);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
up_write(&cmtp_session_sem);
|
||||
return 0;
|
||||
|
||||
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);
|
||||
|
||||
/* Stop session thread */
|
||||
atomic_inc(&session->terminate);
|
||||
wake_up_process(session->task);
|
||||
} else
|
||||
err = -ENOENT;
|
||||
|
||||
up_read(&cmtp_session_sem);
|
||||
return err;
|
||||
}
|
||||
|
||||
int cmtp_get_connlist(struct cmtp_connlist_req *req)
|
||||
{
|
||||
struct cmtp_session *session;
|
||||
int err = 0, n = 0;
|
||||
|
||||
BT_DBG("");
|
||||
|
||||
down_read(&cmtp_session_sem);
|
||||
|
||||
list_for_each_entry(session, &cmtp_session_list, list) {
|
||||
struct cmtp_conninfo ci;
|
||||
|
||||
__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)
|
||||
{
|
||||
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");
|
269
net/bluetooth/cmtp/sock.c
Normal file
269
net/bluetooth/cmtp/sock.c
Normal file
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
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/export.h>
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.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 <linux/gfp.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
#include <linux/isdn/capilli.h>
|
||||
|
||||
|
||||
#include "cmtp.h"
|
||||
|
||||
static struct bt_sock_list cmtp_sk_list = {
|
||||
.lock = __RW_LOCK_UNLOCKED(cmtp_sk_list.lock)
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
bt_sock_unlink(&cmtp_sk_list, sk);
|
||||
|
||||
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 -EPERM;
|
||||
|
||||
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) {
|
||||
sockfd_put(nsock);
|
||||
return -EBADFD;
|
||||
}
|
||||
|
||||
err = cmtp_add_connection(&ca, nsock);
|
||||
if (!err) {
|
||||
if (copy_to_user(argp, &ca, sizeof(ca)))
|
||||
err = -EFAULT;
|
||||
} else
|
||||
sockfd_put(nsock);
|
||||
|
||||
return err;
|
||||
|
||||
case CMTPCONNDEL:
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
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;
|
||||
u32 uci;
|
||||
int err;
|
||||
|
||||
if (get_user(cl.cnum, (u32 __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, (u32 __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 net *net, struct socket *sock, int protocol,
|
||||
int kern)
|
||||
{
|
||||
struct sock *sk;
|
||||
|
||||
BT_DBG("sock %p", sock);
|
||||
|
||||
if (sock->type != SOCK_RAW)
|
||||
return -ESOCKTNOSUPPORT;
|
||||
|
||||
sk = sk_alloc(net, PF_BLUETOOTH, GFP_ATOMIC, &cmtp_proto);
|
||||
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;
|
||||
|
||||
bt_sock_link(&cmtp_sk_list, sk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const 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) {
|
||||
BT_ERR("Can't register CMTP socket");
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = bt_procfs_init(&init_net, "cmtp", &cmtp_sk_list, NULL);
|
||||
if (err < 0) {
|
||||
BT_ERR("Failed to create CMTP proc file");
|
||||
bt_sock_unregister(BTPROTO_HIDP);
|
||||
goto error;
|
||||
}
|
||||
|
||||
BT_INFO("CMTP socket layer initialized");
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
proto_unregister(&cmtp_proto);
|
||||
return err;
|
||||
}
|
||||
|
||||
void cmtp_cleanup_sockets(void)
|
||||
{
|
||||
bt_procfs_cleanup(&init_net, "cmtp");
|
||||
bt_sock_unregister(BTPROTO_CMTP);
|
||||
proto_unregister(&cmtp_proto);
|
||||
}
|
1377
net/bluetooth/hci_conn.c
Normal file
1377
net/bluetooth/hci_conn.c
Normal file
File diff suppressed because it is too large
Load diff
5716
net/bluetooth/hci_core.c
Normal file
5716
net/bluetooth/hci_core.c
Normal file
File diff suppressed because it is too large
Load diff
4875
net/bluetooth/hci_event.c
Normal file
4875
net/bluetooth/hci_event.c
Normal file
File diff suppressed because it is too large
Load diff
1264
net/bluetooth/hci_sock.c
Normal file
1264
net/bluetooth/hci_sock.c
Normal file
File diff suppressed because it is too large
Load diff
214
net/bluetooth/hci_sysfs.c
Normal file
214
net/bluetooth/hci_sysfs.c
Normal file
|
@ -0,0 +1,214 @@
|
|||
/* Bluetooth HCI driver model support. */
|
||||
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
|
||||
static struct class *bt_class;
|
||||
|
||||
static inline char *link_typetostr(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case ACL_LINK:
|
||||
return "ACL";
|
||||
case SCO_LINK:
|
||||
return "SCO";
|
||||
case ESCO_LINK:
|
||||
return "eSCO";
|
||||
case LE_LINK:
|
||||
return "LE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t show_link_type(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_conn *conn = to_hci_conn(dev);
|
||||
return sprintf(buf, "%s\n", link_typetostr(conn->type));
|
||||
}
|
||||
|
||||
static ssize_t show_link_address(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_conn *conn = to_hci_conn(dev);
|
||||
return sprintf(buf, "%pMR\n", &conn->dst);
|
||||
}
|
||||
|
||||
#define LINK_ATTR(_name, _mode, _show, _store) \
|
||||
struct device_attribute link_attr_##_name = __ATTR(_name, _mode, _show, _store)
|
||||
|
||||
static LINK_ATTR(type, S_IRUGO, show_link_type, NULL);
|
||||
static LINK_ATTR(address, S_IRUGO, show_link_address, NULL);
|
||||
|
||||
static struct attribute *bt_link_attrs[] = {
|
||||
&link_attr_type.attr,
|
||||
&link_attr_address.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(bt_link);
|
||||
|
||||
static void bt_link_release(struct device *dev)
|
||||
{
|
||||
struct hci_conn *conn = to_hci_conn(dev);
|
||||
kfree(conn);
|
||||
}
|
||||
|
||||
static struct device_type bt_link = {
|
||||
.name = "link",
|
||||
.groups = bt_link_groups,
|
||||
.release = bt_link_release,
|
||||
};
|
||||
|
||||
/*
|
||||
* The rfcomm tty device will possibly retain even when conn
|
||||
* is down, and sysfs doesn't support move zombie device,
|
||||
* so we should move the device before conn device is destroyed.
|
||||
*/
|
||||
static int __match_tty(struct device *dev, void *data)
|
||||
{
|
||||
return !strncmp(dev_name(dev), "rfcomm", 6);
|
||||
}
|
||||
|
||||
void hci_conn_init_sysfs(struct hci_conn *conn)
|
||||
{
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
|
||||
BT_DBG("conn %p", conn);
|
||||
|
||||
conn->dev.type = &bt_link;
|
||||
conn->dev.class = bt_class;
|
||||
conn->dev.parent = &hdev->dev;
|
||||
|
||||
device_initialize(&conn->dev);
|
||||
}
|
||||
|
||||
void hci_conn_add_sysfs(struct hci_conn *conn)
|
||||
{
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
|
||||
BT_DBG("conn %p", conn);
|
||||
|
||||
dev_set_name(&conn->dev, "%s:%d", hdev->name, conn->handle);
|
||||
|
||||
if (device_add(&conn->dev) < 0) {
|
||||
BT_ERR("Failed to register connection device");
|
||||
return;
|
||||
}
|
||||
|
||||
hci_dev_hold(hdev);
|
||||
}
|
||||
|
||||
void hci_conn_del_sysfs(struct hci_conn *conn)
|
||||
{
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
|
||||
if (!device_is_registered(&conn->dev))
|
||||
return;
|
||||
|
||||
while (1) {
|
||||
struct device *dev;
|
||||
|
||||
dev = device_find_child(&conn->dev, NULL, __match_tty);
|
||||
if (!dev)
|
||||
break;
|
||||
device_move(dev, NULL, DPM_ORDER_DEV_LAST);
|
||||
put_device(dev);
|
||||
}
|
||||
|
||||
device_del(&conn->dev);
|
||||
|
||||
hci_dev_put(hdev);
|
||||
}
|
||||
|
||||
static inline char *host_typetostr(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case HCI_BREDR:
|
||||
return "BR/EDR";
|
||||
case HCI_AMP:
|
||||
return "AMP";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t show_type(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_dev *hdev = to_hci_dev(dev);
|
||||
return sprintf(buf, "%s\n", host_typetostr(hdev->dev_type));
|
||||
}
|
||||
|
||||
static ssize_t show_name(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_dev *hdev = to_hci_dev(dev);
|
||||
char name[HCI_MAX_NAME_LENGTH + 1];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < HCI_MAX_NAME_LENGTH; i++)
|
||||
name[i] = hdev->dev_name[i];
|
||||
|
||||
name[HCI_MAX_NAME_LENGTH] = '\0';
|
||||
return sprintf(buf, "%s\n", name);
|
||||
}
|
||||
|
||||
static ssize_t show_address(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_dev *hdev = to_hci_dev(dev);
|
||||
return sprintf(buf, "%pMR\n", &hdev->bdaddr);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
|
||||
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
|
||||
static DEVICE_ATTR(address, S_IRUGO, show_address, NULL);
|
||||
|
||||
static struct attribute *bt_host_attrs[] = {
|
||||
&dev_attr_type.attr,
|
||||
&dev_attr_name.attr,
|
||||
&dev_attr_address.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(bt_host);
|
||||
|
||||
static void bt_host_release(struct device *dev)
|
||||
{
|
||||
struct hci_dev *hdev = to_hci_dev(dev);
|
||||
kfree(hdev);
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
static struct device_type bt_host = {
|
||||
.name = "host",
|
||||
.groups = bt_host_groups,
|
||||
.release = bt_host_release,
|
||||
};
|
||||
|
||||
void hci_init_sysfs(struct hci_dev *hdev)
|
||||
{
|
||||
struct device *dev = &hdev->dev;
|
||||
|
||||
dev->type = &bt_host;
|
||||
dev->class = bt_class;
|
||||
|
||||
__module_get(THIS_MODULE);
|
||||
device_initialize(dev);
|
||||
}
|
||||
|
||||
int __init bt_sysfs_init(void)
|
||||
{
|
||||
bt_class = class_create(THIS_MODULE, "bluetooth");
|
||||
|
||||
return PTR_ERR_OR_ZERO(bt_class);
|
||||
}
|
||||
|
||||
void bt_sysfs_cleanup(void)
|
||||
{
|
||||
class_destroy(bt_class);
|
||||
}
|
12
net/bluetooth/hidp/Kconfig
Normal file
12
net/bluetooth/hidp/Kconfig
Normal file
|
@ -0,0 +1,12 @@
|
|||
config BT_HIDP
|
||||
tristate "HIDP protocol support"
|
||||
depends on BT && 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).
|
||||
|
7
net/bluetooth/hidp/Makefile
Normal file
7
net/bluetooth/hidp/Makefile
Normal file
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Makefile for the Linux Bluetooth HIDP layer
|
||||
#
|
||||
|
||||
obj-$(CONFIG_BT_HIDP) += hidp.o
|
||||
|
||||
hidp-objs := core.o sock.o
|
1439
net/bluetooth/hidp/core.c
Normal file
1439
net/bluetooth/hidp/core.c
Normal file
File diff suppressed because it is too large
Load diff
192
net/bluetooth/hidp/hidp.h
Normal file
192
net/bluetooth/hidp/hidp.h
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
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 <linux/hid.h>
|
||||
#include <linux/kref.h>
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/l2cap.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
|
||||
#define HIDP_WAITING_FOR_RETURN 10
|
||||
#define HIDP_WAITING_FOR_SEND_ACK 11
|
||||
|
||||
struct hidp_connadd_req {
|
||||
int ctrl_sock; /* Connected control socket */
|
||||
int intr_sock; /* Connected 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_connection_add(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock);
|
||||
int hidp_connection_del(struct hidp_conndel_req *req);
|
||||
int hidp_get_connlist(struct hidp_connlist_req *req);
|
||||
int hidp_get_conninfo(struct hidp_conninfo *ci);
|
||||
|
||||
enum hidp_session_state {
|
||||
HIDP_SESSION_IDLING,
|
||||
HIDP_SESSION_PREPARING,
|
||||
HIDP_SESSION_RUNNING,
|
||||
};
|
||||
|
||||
/* HIDP session defines */
|
||||
struct hidp_session {
|
||||
struct list_head list;
|
||||
struct kref ref;
|
||||
|
||||
/* runtime management */
|
||||
atomic_t state;
|
||||
wait_queue_head_t state_queue;
|
||||
atomic_t terminate;
|
||||
struct task_struct *task;
|
||||
unsigned long flags;
|
||||
|
||||
/* connection management */
|
||||
bdaddr_t bdaddr;
|
||||
struct l2cap_conn *conn;
|
||||
struct l2cap_user user;
|
||||
struct socket *ctrl_sock;
|
||||
struct socket *intr_sock;
|
||||
struct sk_buff_head ctrl_transmit;
|
||||
struct sk_buff_head intr_transmit;
|
||||
uint ctrl_mtu;
|
||||
uint intr_mtu;
|
||||
unsigned long idle_to;
|
||||
|
||||
/* device management */
|
||||
struct work_struct dev_init;
|
||||
struct input_dev *input;
|
||||
struct hid_device *hid;
|
||||
struct timer_list timer;
|
||||
|
||||
/* Report descriptor */
|
||||
__u8 *rd_data;
|
||||
uint rd_size;
|
||||
|
||||
/* session data */
|
||||
unsigned char keys[8];
|
||||
unsigned char leds;
|
||||
|
||||
/* Used in hidp_get_raw_report() */
|
||||
int waiting_report_type; /* HIDP_DATA_RTYPE_* */
|
||||
int waiting_report_number; /* -1 for not numbered */
|
||||
struct mutex report_mutex;
|
||||
struct sk_buff *report_return;
|
||||
wait_queue_head_t report_queue;
|
||||
|
||||
/* Used in hidp_output_raw_report() */
|
||||
int output_report_success; /* boolean */
|
||||
|
||||
/* temporary input buffer */
|
||||
u8 input_buf[HID_MAX_BUFFER_SIZE];
|
||||
};
|
||||
|
||||
/* HIDP init defines */
|
||||
int __init hidp_init_sockets(void);
|
||||
void __exit hidp_cleanup_sockets(void);
|
||||
|
||||
#endif /* __HIDP_H */
|
299
net/bluetooth/hidp/sock.c
Normal file
299
net/bluetooth/hidp/sock.c
Normal file
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
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/export.h>
|
||||
#include <linux/file.h>
|
||||
|
||||
#include "hidp.h"
|
||||
|
||||
static struct bt_sock_list hidp_sk_list = {
|
||||
.lock = __RW_LOCK_UNLOCKED(hidp_sk_list.lock)
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
bt_sock_unlink(&hidp_sk_list, sk);
|
||||
|
||||
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 -EPERM;
|
||||
|
||||
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) {
|
||||
sockfd_put(csock);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = hidp_connection_add(&ca, csock, isock);
|
||||
if (!err && copy_to_user(argp, &ca, sizeof(ca)))
|
||||
err = -EFAULT;
|
||||
|
||||
sockfd_put(csock);
|
||||
sockfd_put(isock);
|
||||
|
||||
return err;
|
||||
|
||||
case HIDPCONNDEL:
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (copy_from_user(&cd, argp, sizeof(cd)))
|
||||
return -EFAULT;
|
||||
|
||||
return hidp_connection_del(&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; /* Connected 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;
|
||||
u32 uci;
|
||||
int err;
|
||||
|
||||
if (get_user(cl.cnum, (u32 __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, (u32 __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 net *net, struct socket *sock, int protocol,
|
||||
int kern)
|
||||
{
|
||||
struct sock *sk;
|
||||
|
||||
BT_DBG("sock %p", sock);
|
||||
|
||||
if (sock->type != SOCK_RAW)
|
||||
return -ESOCKTNOSUPPORT;
|
||||
|
||||
sk = sk_alloc(net, PF_BLUETOOTH, GFP_ATOMIC, &hidp_proto);
|
||||
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;
|
||||
|
||||
bt_sock_link(&hidp_sk_list, sk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const 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) {
|
||||
BT_ERR("Can't register HIDP socket");
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = bt_procfs_init(&init_net, "hidp", &hidp_sk_list, NULL);
|
||||
if (err < 0) {
|
||||
BT_ERR("Failed to create HIDP proc file");
|
||||
bt_sock_unregister(BTPROTO_HIDP);
|
||||
goto error;
|
||||
}
|
||||
|
||||
BT_INFO("HIDP socket layer initialized");
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
proto_unregister(&hidp_proto);
|
||||
return err;
|
||||
}
|
||||
|
||||
void __exit hidp_cleanup_sockets(void)
|
||||
{
|
||||
bt_procfs_cleanup(&init_net, "hidp");
|
||||
bt_sock_unregister(BTPROTO_HIDP);
|
||||
proto_unregister(&hidp_proto);
|
||||
}
|
7548
net/bluetooth/l2cap_core.c
Normal file
7548
net/bluetooth/l2cap_core.c
Normal file
File diff suppressed because it is too large
Load diff
1636
net/bluetooth/l2cap_sock.c
Normal file
1636
net/bluetooth/l2cap_sock.c
Normal file
File diff suppressed because it is too large
Load diff
168
net/bluetooth/lib.c
Normal file
168
net/bluetooth/lib.c
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
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. */
|
||||
|
||||
#define pr_fmt(fmt) "Bluetooth: " fmt
|
||||
|
||||
#include <linux/export.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);
|
||||
|
||||
/* Bluetooth error codes to Unix errno mapping */
|
||||
int bt_to_errno(__u16 code)
|
||||
{
|
||||
switch (code) {
|
||||
case 0:
|
||||
return 0;
|
||||
|
||||
case 0x01:
|
||||
return EBADRQC;
|
||||
|
||||
case 0x02:
|
||||
return ENOTCONN;
|
||||
|
||||
case 0x03:
|
||||
return EIO;
|
||||
|
||||
case 0x04:
|
||||
case 0x3c:
|
||||
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_to_errno);
|
||||
|
||||
void bt_info(const char *format, ...)
|
||||
{
|
||||
struct va_format vaf;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
|
||||
vaf.fmt = format;
|
||||
vaf.va = &args;
|
||||
|
||||
pr_info("%pV", &vaf);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
EXPORT_SYMBOL(bt_info);
|
||||
|
||||
void bt_err(const char *format, ...)
|
||||
{
|
||||
struct va_format vaf;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
|
||||
vaf.fmt = format;
|
||||
vaf.va = &args;
|
||||
|
||||
pr_err("%pV", &vaf);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
EXPORT_SYMBOL(bt_err);
|
6855
net/bluetooth/mgmt.c
Normal file
6855
net/bluetooth/mgmt.c
Normal file
File diff suppressed because it is too large
Load diff
18
net/bluetooth/rfcomm/Kconfig
Normal file
18
net/bluetooth/rfcomm/Kconfig
Normal file
|
@ -0,0 +1,18 @@
|
|||
config BT_RFCOMM
|
||||
tristate "RFCOMM protocol support"
|
||||
depends on BT
|
||||
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
|
||||
depends on TTY
|
||||
help
|
||||
This option enables TTY emulation support for RFCOMM channels.
|
||||
|
8
net/bluetooth/rfcomm/Makefile
Normal file
8
net/bluetooth/rfcomm/Makefile
Normal 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
|
2277
net/bluetooth/rfcomm/core.c
Normal file
2277
net/bluetooth/rfcomm/core.c
Normal file
File diff suppressed because it is too large
Load diff
1103
net/bluetooth/rfcomm/sock.c
Normal file
1103
net/bluetooth/rfcomm/sock.c
Normal file
File diff suppressed because it is too large
Load diff
1174
net/bluetooth/rfcomm/tty.c
Normal file
1174
net/bluetooth/rfcomm/tty.c
Normal file
File diff suppressed because it is too large
Load diff
1231
net/bluetooth/sco.c
Normal file
1231
net/bluetooth/sco.c
Normal file
File diff suppressed because it is too large
Load diff
1755
net/bluetooth/smp.c
Normal file
1755
net/bluetooth/smp.c
Normal file
File diff suppressed because it is too large
Load diff
147
net/bluetooth/smp.h
Normal file
147
net/bluetooth/smp.h
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
BlueZ - Bluetooth protocol stack for Linux
|
||||
Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
|
||||
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 __SMP_H
|
||||
#define __SMP_H
|
||||
|
||||
struct smp_command_hdr {
|
||||
__u8 code;
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_PAIRING_REQ 0x01
|
||||
#define SMP_CMD_PAIRING_RSP 0x02
|
||||
struct smp_cmd_pairing {
|
||||
__u8 io_capability;
|
||||
__u8 oob_flag;
|
||||
__u8 auth_req;
|
||||
__u8 max_key_size;
|
||||
__u8 init_key_dist;
|
||||
__u8 resp_key_dist;
|
||||
} __packed;
|
||||
|
||||
#define SMP_IO_DISPLAY_ONLY 0x00
|
||||
#define SMP_IO_DISPLAY_YESNO 0x01
|
||||
#define SMP_IO_KEYBOARD_ONLY 0x02
|
||||
#define SMP_IO_NO_INPUT_OUTPUT 0x03
|
||||
#define SMP_IO_KEYBOARD_DISPLAY 0x04
|
||||
|
||||
#define SMP_OOB_NOT_PRESENT 0x00
|
||||
#define SMP_OOB_PRESENT 0x01
|
||||
|
||||
#define SMP_DIST_ENC_KEY 0x01
|
||||
#define SMP_DIST_ID_KEY 0x02
|
||||
#define SMP_DIST_SIGN 0x04
|
||||
|
||||
#define SMP_AUTH_NONE 0x00
|
||||
#define SMP_AUTH_BONDING 0x01
|
||||
#define SMP_AUTH_MITM 0x04
|
||||
|
||||
#define SMP_CMD_PAIRING_CONFIRM 0x03
|
||||
struct smp_cmd_pairing_confirm {
|
||||
__u8 confirm_val[16];
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_PAIRING_RANDOM 0x04
|
||||
struct smp_cmd_pairing_random {
|
||||
__u8 rand_val[16];
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_PAIRING_FAIL 0x05
|
||||
struct smp_cmd_pairing_fail {
|
||||
__u8 reason;
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_ENCRYPT_INFO 0x06
|
||||
struct smp_cmd_encrypt_info {
|
||||
__u8 ltk[16];
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_MASTER_IDENT 0x07
|
||||
struct smp_cmd_master_ident {
|
||||
__le16 ediv;
|
||||
__le64 rand;
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_IDENT_INFO 0x08
|
||||
struct smp_cmd_ident_info {
|
||||
__u8 irk[16];
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_IDENT_ADDR_INFO 0x09
|
||||
struct smp_cmd_ident_addr_info {
|
||||
__u8 addr_type;
|
||||
bdaddr_t bdaddr;
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_SIGN_INFO 0x0a
|
||||
struct smp_cmd_sign_info {
|
||||
__u8 csrk[16];
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_SECURITY_REQ 0x0b
|
||||
struct smp_cmd_security_req {
|
||||
__u8 auth_req;
|
||||
} __packed;
|
||||
|
||||
#define SMP_CMD_MAX 0x0b
|
||||
|
||||
#define SMP_PASSKEY_ENTRY_FAILED 0x01
|
||||
#define SMP_OOB_NOT_AVAIL 0x02
|
||||
#define SMP_AUTH_REQUIREMENTS 0x03
|
||||
#define SMP_CONFIRM_FAILED 0x04
|
||||
#define SMP_PAIRING_NOTSUPP 0x05
|
||||
#define SMP_ENC_KEY_SIZE 0x06
|
||||
#define SMP_CMD_NOTSUPP 0x07
|
||||
#define SMP_UNSPECIFIED 0x08
|
||||
#define SMP_REPEATED_ATTEMPTS 0x09
|
||||
#define SMP_INVALID_PARAMS 0x0a
|
||||
|
||||
#define SMP_MIN_ENC_KEY_SIZE 7
|
||||
#define SMP_MAX_ENC_KEY_SIZE 16
|
||||
|
||||
/* LTK types used in internal storage (struct smp_ltk) */
|
||||
enum {
|
||||
SMP_STK,
|
||||
SMP_LTK,
|
||||
SMP_LTK_SLAVE,
|
||||
};
|
||||
|
||||
static inline u8 smp_ltk_sec_level(struct smp_ltk *key)
|
||||
{
|
||||
if (key->authenticated)
|
||||
return BT_SECURITY_HIGH;
|
||||
|
||||
return BT_SECURITY_MEDIUM;
|
||||
}
|
||||
|
||||
/* SMP Commands */
|
||||
bool smp_sufficient_security(struct hci_conn *hcon, u8 sec_level);
|
||||
int smp_conn_security(struct hci_conn *hcon, __u8 sec_level);
|
||||
int smp_user_confirm_reply(struct hci_conn *conn, u16 mgmt_op, __le32 passkey);
|
||||
|
||||
bool smp_irk_matches(struct hci_dev *hdev, u8 irk[16], bdaddr_t *bdaddr);
|
||||
int smp_generate_rpa(struct hci_dev *hdev, u8 irk[16], bdaddr_t *rpa);
|
||||
|
||||
int smp_register(struct hci_dev *hdev);
|
||||
void smp_unregister(struct hci_dev *hdev);
|
||||
|
||||
#endif /* __SMP_H */
|
Loading…
Add table
Add a link
Reference in a new issue