2005-04-17 02:20:36 +04:00
/*
*
2005-10-28 21:20:45 +04:00
* Bluetooth HCI UART driver
*
* Copyright ( C ) 2000 - 2001 Qualcomm Incorporated
* Copyright ( C ) 2002 - 2003 Maxim Krasnyansky < maxk @ qualcomm . com >
* Copyright ( C ) 2004 - 2005 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 as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*
2005-04-17 02:20:36 +04:00
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/fcntl.h>
# include <linux/interrupt.h>
# include <linux/ptrace.h>
# include <linux/poll.h>
# include <linux/slab.h>
# include <linux/tty.h>
# include <linux/errno.h>
# include <linux/string.h>
# include <linux/signal.h>
# include <linux/ioctl.h>
# include <linux/skbuff.h>
2015-04-06 09:44:59 +03:00
# include <asm/unaligned.h>
2005-04-17 02:20:36 +04:00
# include <net/bluetooth/bluetooth.h>
# include <net/bluetooth/hci_core.h>
2005-10-28 21:20:45 +04:00
2005-04-17 02:20:36 +04:00
# include "hci_uart.h"
2005-10-28 21:20:45 +04:00
struct h4_struct {
2015-04-05 07:59:25 +03:00
struct sk_buff * rx_skb ;
2005-10-28 21:20:45 +04:00
struct sk_buff_head txq ;
} ;
2005-04-17 02:20:36 +04:00
/* Initialize protocol */
static int h4_open ( struct hci_uart * hu )
{
struct h4_struct * h4 ;
2005-10-28 21:20:45 +04:00
2005-04-17 02:20:36 +04:00
BT_DBG ( " hu %p " , hu ) ;
2005-10-28 21:20:45 +04:00
2012-01-07 18:19:38 +04:00
h4 = kzalloc ( sizeof ( * h4 ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! h4 )
return - ENOMEM ;
2005-10-28 21:20:45 +04:00
2005-04-17 02:20:36 +04:00
skb_queue_head_init ( & h4 - > txq ) ;
hu - > priv = h4 ;
return 0 ;
}
/* Flush protocol data */
static int h4_flush ( struct hci_uart * hu )
{
struct h4_struct * h4 = hu - > priv ;
BT_DBG ( " hu %p " , hu ) ;
2005-10-28 21:20:45 +04:00
2005-04-17 02:20:36 +04:00
skb_queue_purge ( & h4 - > txq ) ;
2005-10-28 21:20:45 +04:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
/* Close protocol */
static int h4_close ( struct hci_uart * hu )
{
struct h4_struct * h4 = hu - > priv ;
2005-10-28 21:20:45 +04:00
2005-04-17 02:20:36 +04:00
hu - > priv = NULL ;
BT_DBG ( " hu %p " , hu ) ;
skb_queue_purge ( & h4 - > txq ) ;
2005-10-28 21:20:45 +04:00
2015-04-05 07:59:25 +03:00
kfree_skb ( h4 - > rx_skb ) ;
2005-04-17 02:20:36 +04:00
hu - > priv = NULL ;
kfree ( h4 ) ;
2005-10-28 21:20:45 +04:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
/* Enqueue frame for transmittion (padding, crc, etc) */
static int h4_enqueue ( struct hci_uart * hu , struct sk_buff * skb )
{
struct h4_struct * h4 = hu - > priv ;
BT_DBG ( " hu %p skb %p " , hu , skb ) ;
/* Prepend skb with frame type */
2005-08-10 07:30:28 +04:00
memcpy ( skb_push ( skb , 1 ) , & bt_cb ( skb ) - > pkt_type , 1 ) ;
2005-04-17 02:20:36 +04:00
skb_queue_tail ( & h4 - > txq , skb ) ;
2005-10-28 21:20:45 +04:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
2015-04-06 09:44:59 +03:00
static const struct h4_recv_pkt h4_recv_pkts [ ] = {
{ H4_RECV_ACL , . recv = hci_recv_frame } ,
{ H4_RECV_SCO , . recv = hci_recv_frame } ,
{ H4_RECV_EVENT , . recv = hci_recv_frame } ,
} ;
2005-04-17 02:20:36 +04:00
/* Recv data */
2015-04-05 06:59:41 +03:00
static int h4_recv ( struct hci_uart * hu , const void * data , int count )
2005-04-17 02:20:36 +04:00
{
2015-04-05 07:59:25 +03:00
struct h4_struct * h4 = hu - > priv ;
2011-04-08 01:53:45 +04:00
2013-04-02 16:24:21 +04:00
if ( ! test_bit ( HCI_UART_REGISTERED , & hu - > flags ) )
return - EUNATCH ;
2015-04-06 09:44:59 +03:00
h4 - > rx_skb = h4_recv_buf ( hu - > hdev , h4 - > rx_skb , data , count ,
h4_recv_pkts , ARRAY_SIZE ( h4_recv_pkts ) ) ;
2015-04-05 07:59:25 +03:00
if ( IS_ERR ( h4 - > rx_skb ) ) {
int err = PTR_ERR ( h4 - > rx_skb ) ;
BT_ERR ( " %s: Frame reassembly failed (%d) " , hu - > hdev - > name , err ) ;
2015-06-17 15:10:39 +03:00
h4 - > rx_skb = NULL ;
2015-04-05 07:59:25 +03:00
return err ;
2011-04-08 01:53:45 +04:00
}
2005-10-28 21:20:45 +04:00
2005-04-17 02:20:36 +04:00
return count ;
}
static struct sk_buff * h4_dequeue ( struct hci_uart * hu )
{
struct h4_struct * h4 = hu - > priv ;
return skb_dequeue ( & h4 - > txq ) ;
}
2015-04-05 08:11:43 +03:00
static const struct hci_uart_proto h4p = {
2005-10-28 21:20:45 +04:00
. id = HCI_UART_H4 ,
2015-04-05 08:27:34 +03:00
. name = " H4 " ,
2005-10-28 21:20:45 +04:00
. open = h4_open ,
. close = h4_close ,
. recv = h4_recv ,
. enqueue = h4_enqueue ,
. dequeue = h4_dequeue ,
. flush = h4_flush ,
2005-04-17 02:20:36 +04:00
} ;
2005-10-28 21:20:45 +04:00
2010-07-24 09:04:44 +04:00
int __init h4_init ( void )
2005-04-17 02:20:36 +04:00
{
2015-04-05 08:27:35 +03:00
return hci_uart_register_proto ( & h4p ) ;
2005-04-17 02:20:36 +04:00
}
2010-07-24 09:04:44 +04:00
int __exit h4_deinit ( void )
2005-04-17 02:20:36 +04:00
{
return hci_uart_unregister_proto ( & h4p ) ;
}
2015-04-05 07:59:24 +03:00
struct sk_buff * h4_recv_buf ( struct hci_dev * hdev , struct sk_buff * skb ,
2015-04-06 09:44:59 +03:00
const unsigned char * buffer , int count ,
const struct h4_recv_pkt * pkts , int pkts_count )
2015-04-05 07:59:24 +03:00
{
while ( count ) {
2015-04-06 09:44:59 +03:00
int i , len ;
2015-04-05 07:59:24 +03:00
if ( ! skb ) {
2015-04-06 09:44:59 +03:00
for ( i = 0 ; i < pkts_count ; i + + ) {
if ( buffer [ 0 ] ! = ( & pkts [ i ] ) - > type )
continue ;
2015-04-05 07:59:24 +03:00
2015-04-06 09:44:59 +03:00
skb = bt_skb_alloc ( ( & pkts [ i ] ) - > maxlen ,
2015-04-05 07:59:24 +03:00
GFP_ATOMIC ) ;
if ( ! skb )
return ERR_PTR ( - ENOMEM ) ;
2015-04-06 09:44:59 +03:00
bt_cb ( skb ) - > pkt_type = ( & pkts [ i ] ) - > type ;
bt_cb ( skb ) - > expect = ( & pkts [ i ] ) - > hlen ;
2015-04-05 07:59:24 +03:00
break ;
2015-04-06 09:44:59 +03:00
}
2015-04-05 07:59:24 +03:00
2015-04-06 09:44:59 +03:00
/* Check for invalid packet type */
if ( ! skb )
2015-04-05 07:59:24 +03:00
return ERR_PTR ( - EILSEQ ) ;
count - = 1 ;
buffer + = 1 ;
}
2015-04-06 09:44:59 +03:00
len = min_t ( uint , bt_cb ( skb ) - > expect - skb - > len , count ) ;
2015-04-05 07:59:24 +03:00
memcpy ( skb_put ( skb , len ) , buffer , len ) ;
count - = len ;
buffer + = len ;
2015-04-06 09:44:59 +03:00
/* Check for partial packet */
if ( skb - > len < bt_cb ( skb ) - > expect )
continue ;
for ( i = 0 ; i < pkts_count ; i + + ) {
if ( bt_cb ( skb ) - > pkt_type = = ( & pkts [ i ] ) - > type )
break ;
}
if ( i > = pkts_count ) {
kfree_skb ( skb ) ;
return ERR_PTR ( - EILSEQ ) ;
}
2015-04-05 07:59:24 +03:00
2015-04-06 09:44:59 +03:00
if ( skb - > len = = ( & pkts [ i ] ) - > hlen ) {
u16 dlen ;
2015-04-05 07:59:24 +03:00
2015-04-06 09:44:59 +03:00
switch ( ( & pkts [ i ] ) - > lsize ) {
case 0 :
/* No variable data length */
2015-08-24 19:57:57 +03:00
dlen = 0 ;
2015-04-06 09:44:59 +03:00
break ;
case 1 :
/* Single octet variable length */
dlen = skb - > data [ ( & pkts [ i ] ) - > loff ] ;
bt_cb ( skb ) - > expect + = dlen ;
2015-04-05 07:59:24 +03:00
2015-04-06 09:44:59 +03:00
if ( skb_tailroom ( skb ) < dlen ) {
2015-04-05 07:59:24 +03:00
kfree_skb ( skb ) ;
return ERR_PTR ( - EMSGSIZE ) ;
}
2015-04-06 09:44:59 +03:00
break ;
case 2 :
/* Double octet variable length */
dlen = get_unaligned_le16 ( skb - > data +
( & pkts [ i ] ) - > loff ) ;
bt_cb ( skb ) - > expect + = dlen ;
2015-04-05 07:59:24 +03:00
2015-04-06 09:44:59 +03:00
if ( skb_tailroom ( skb ) < dlen ) {
2015-04-05 07:59:24 +03:00
kfree_skb ( skb ) ;
return ERR_PTR ( - EMSGSIZE ) ;
}
2015-04-06 09:44:59 +03:00
break ;
default :
/* Unsupported variable length */
kfree_skb ( skb ) ;
return ERR_PTR ( - EILSEQ ) ;
2015-04-05 07:59:24 +03:00
}
2015-08-24 19:57:57 +03:00
if ( ! dlen ) {
/* No more data, complete frame */
( & pkts [ i ] ) - > recv ( hdev , skb ) ;
skb = NULL ;
}
2015-04-06 09:44:59 +03:00
} else {
2015-04-05 07:59:24 +03:00
/* Complete frame */
2015-04-06 09:44:59 +03:00
( & pkts [ i ] ) - > recv ( hdev , skb ) ;
2015-04-05 07:59:24 +03:00
skb = NULL ;
}
}
return skb ;
}
2015-10-08 04:06:53 +03:00
EXPORT_SYMBOL_GPL ( h4_recv_buf ) ;