2007-02-09 17:24:33 +03:00
/*
2005-04-17 02:20:36 +04:00
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
2007-02-09 17:24:33 +03:00
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
2005-04-17 02:20:36 +04:00
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE .
2007-02-09 17:24:33 +03:00
ALL LIABILITY , INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS ,
COPYRIGHTS , TRADEMARKS OR OTHER RIGHTS , RELATING TO USE OF THIS
2005-04-17 02:20:36 +04:00
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>
2007-07-17 15:03:35 +04:00
# include <linux/freezer.h>
2005-04-17 02:20:36 +04:00
# include <linux/skbuff.h>
# include <linux/socket.h>
# include <linux/ioctl.h>
# include <linux/file.h>
# include <linux/init.h>
2011-03-21 16:20:06 +03:00
# include <linux/kthread.h>
2005-04-17 02:20:36 +04:00
# 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 ;
struct list_head * p ;
BT_DBG ( " " ) ;
list_for_each ( p , & cmtp_session_list ) {
session = list_entry ( p , struct cmtp_session , list ) ;
if ( ! bacmp ( bdaddr , & session - > bdaddr ) )
return session ;
}
return NULL ;
}
static void __cmtp_link_session ( struct cmtp_session * session )
{
__module_get ( THIS_MODULE ) ;
list_add ( & session - > list , & cmtp_session_list ) ;
}
static void __cmtp_unlink_session ( struct cmtp_session * session )
{
list_del ( & session - > list ) ;
module_put ( THIS_MODULE ) ;
}
static void __cmtp_copy_session ( struct cmtp_session * session , struct cmtp_conninfo * ci )
{
2010-10-30 18:26:26 +04:00
memset ( ci , 0 , sizeof ( * ci ) ) ;
2005-04-17 02:20:36 +04:00
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 ;
2011-01-11 18:20:20 +03:00
nskb = alloc_skb ( size , GFP_ATOMIC ) ;
if ( ! nskb ) {
2005-04-17 02:20:36 +04:00
BT_ERR ( " Can't allocate memory for CAPI message " ) ;
return ;
}
if ( skb & & ( skb - > len > 0 ) )
2007-03-28 01:55:52 +04:00
skb_copy_from_linear_data ( skb , skb_put ( nskb , skb - > len ) , skb - > len ) ;
2005-04-17 02:20:36 +04:00
memcpy ( skb_put ( nskb , count ) , buf , count ) ;
session - > reassembly [ id ] = nskb ;
2009-02-25 13:29:52 +03:00
kfree_skb ( skb ) ;
2005-04-17 02:20:36 +04:00
}
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 ) ;
}
2005-07-09 01:57:23 +04:00
static void cmtp_process_transmit ( struct cmtp_session * session )
2005-04-17 02:20:36 +04:00
{
struct sk_buff * skb , * nskb ;
unsigned char * hdr ;
unsigned int size , tail ;
BT_DBG ( " session %p " , session ) ;
2011-01-11 18:20:20 +03:00
nskb = alloc_skb ( session - > mtu , GFP_ATOMIC ) ;
if ( ! nskb ) {
2005-04-17 02:20:36 +04:00
BT_ERR ( " Can't allocate memory for new frame " ) ;
2005-07-09 01:57:23 +04:00
return ;
2005-04-17 02:20:36 +04:00
}
while ( ( skb = skb_dequeue ( & session - > transmit ) ) ) {
struct cmtp_scb * scb = ( void * ) skb - > cb ;
2011-01-11 18:20:20 +03:00
tail = session - > mtu - nskb - > len ;
if ( tail < 5 ) {
2005-04-17 02:20:36 +04:00
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 ) ;
2011-03-21 16:20:03 +03:00
if ( scb - > id < 0 ) {
scb - > id = cmtp_alloc_block_id ( session ) ;
if ( scb - > id < 0 ) {
skb_queue_head ( & session - > transmit , skb ) ;
break ;
}
2005-04-17 02:20:36 +04:00
}
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 ;
}
2007-03-28 01:55:52 +04:00
skb_copy_from_linear_data ( skb , skb_put ( nskb , size ) , size ) ;
2005-04-17 02:20:36 +04:00
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 ) ;
2010-04-20 17:03:51 +04:00
add_wait_queue ( sk_sleep ( sk ) , & wait ) ;
2011-03-21 16:20:06 +03:00
while ( ! kthread_should_stop ( ) ) {
2005-04-17 02:20:36 +04:00
set_current_state ( TASK_INTERRUPTIBLE ) ;
if ( sk - > sk_state ! = BT_CONNECTED )
break ;
while ( ( skb = skb_dequeue ( & sk - > sk_receive_queue ) ) ) {
skb_orphan ( skb ) ;
cmtp_recv_frame ( session , skb ) ;
}
cmtp_process_transmit ( session ) ;
schedule ( ) ;
}
set_current_state ( TASK_RUNNING ) ;
2010-04-20 17:03:51 +04:00
remove_wait_queue ( sk_sleep ( sk ) , & wait ) ;
2005-04-17 02:20:36 +04:00
down_write ( & cmtp_session_sem ) ;
if ( ! ( session - > flags & ( 1 < < CMTP_LOOPBACK ) ) )
cmtp_detach_device ( session ) ;
fput ( session - > sock - > file ) ;
__cmtp_unlink_session ( session ) ;
up_write ( & cmtp_session_sem ) ;
kfree ( session ) ;
return 0 ;
}
int cmtp_add_connection ( struct cmtp_connadd_req * req , struct socket * sock )
{
struct cmtp_session * session , * s ;
int i , err ;
BT_DBG ( " " ) ;
2006-07-06 17:40:09 +04:00
session = kzalloc ( sizeof ( struct cmtp_session ) , GFP_KERNEL ) ;
2007-02-09 17:24:33 +03:00
if ( ! session )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
down_write ( & cmtp_session_sem ) ;
s = __cmtp_get_session ( & bt_sk ( sock - > sk ) - > dst ) ;
if ( s & & s - > state = = BT_CONNECTED ) {
err = - EEXIST ;
goto failed ;
}
bacpy ( & session - > bdaddr , & bt_sk ( sock - > sk ) - > dst ) ;
2011-04-14 00:20:49 +04:00
session - > mtu = min_t ( uint , l2cap_pi ( sock - > sk ) - > chan - > omtu ,
l2cap_pi ( sock - > sk ) - > chan - > imtu ) ;
2005-04-17 02:20:36 +04:00
BT_DBG ( " mtu %d " , session - > mtu ) ;
2010-09-04 01:29:46 +04:00
sprintf ( session - > name , " %s " , batostr ( & bt_sk ( sock - > sk ) - > dst ) ) ;
2005-04-17 02:20:36 +04:00
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 ) ;
2011-03-21 16:20:06 +03:00
session - > task = kthread_run ( cmtp_session , session , " kcmtpd_ctr_%d " ,
session - > num ) ;
if ( IS_ERR ( session - > task ) ) {
err = PTR_ERR ( session - > task ) ;
2005-04-17 02:20:36 +04:00
goto unlink ;
2011-03-21 16:20:06 +03:00
}
2005-04-17 02:20:36 +04:00
if ( ! ( session - > flags & ( 1 < < CMTP_LOOPBACK ) ) ) {
err = cmtp_attach_device ( session ) ;
if ( err < 0 )
goto detach ;
}
up_write ( & cmtp_session_sem ) ;
return 0 ;
detach :
cmtp_detach_device ( session ) ;
unlink :
__cmtp_unlink_session ( session ) ;
failed :
up_write ( & cmtp_session_sem ) ;
kfree ( session ) ;
return err ;
}
int cmtp_del_connection ( struct cmtp_conndel_req * req )
{
struct cmtp_session * session ;
int err = 0 ;
BT_DBG ( " " ) ;
down_read ( & cmtp_session_sem ) ;
session = __cmtp_get_session ( & req - > bdaddr ) ;
if ( session ) {
/* Flush the transmit queue */
skb_queue_purge ( & session - > transmit ) ;
2011-03-21 16:20:06 +03:00
/* Stop session thread */
kthread_stop ( session - > task ) ;
2005-04-17 02:20:36 +04:00
} else
err = - ENOENT ;
up_read ( & cmtp_session_sem ) ;
return err ;
}
int cmtp_get_connlist ( struct cmtp_connlist_req * req )
{
struct list_head * p ;
int err = 0 , n = 0 ;
BT_DBG ( " " ) ;
down_read ( & cmtp_session_sem ) ;
list_for_each ( p , & cmtp_session_list ) {
struct cmtp_session * session ;
struct cmtp_conninfo ci ;
session = list_entry ( p , struct cmtp_session , list ) ;
__cmtp_copy_session ( session , & ci ) ;
if ( copy_to_user ( req - > ci , & ci , sizeof ( ci ) ) ) {
err = - EFAULT ;
break ;
}
if ( + + n > = req - > cnum )
break ;
req - > ci + + ;
}
req - > cnum = n ;
up_read ( & cmtp_session_sem ) ;
return err ;
}
int cmtp_get_conninfo ( struct cmtp_conninfo * ci )
{
struct cmtp_session * session ;
int err = 0 ;
down_read ( & cmtp_session_sem ) ;
session = __cmtp_get_session ( & ci - > bdaddr ) ;
if ( session )
__cmtp_copy_session ( session , ci ) ;
else
err = - ENOENT ;
up_read ( & cmtp_session_sem ) ;
return err ;
}
static int __init cmtp_init ( void )
{
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 " ) ;