2019-05-27 09:55:05 +03:00
/* SPDX-License-Identifier: GPL-2.0-or-later */
2018-03-24 12:19:53 +03:00
/*
*
* Generic Bluetooth HCI UART driver
*
* Copyright ( C ) 2015 - 2018 Intel Corporation
*/
# include <asm/unaligned.h>
struct h4_recv_pkt {
u8 type ; /* Packet type */
u8 hlen ; /* Header length */
u8 loff ; /* Data length offset in header */
u8 lsize ; /* Data length field size */
u16 maxlen ; /* Max overall packet length */
int ( * recv ) ( struct hci_dev * hdev , struct sk_buff * skb ) ;
} ;
# define H4_RECV_ACL \
. type = HCI_ACLDATA_PKT , \
. hlen = HCI_ACL_HDR_SIZE , \
. loff = 2 , \
. lsize = 2 , \
. maxlen = HCI_MAX_FRAME_SIZE \
# define H4_RECV_SCO \
. type = HCI_SCODATA_PKT , \
. hlen = HCI_SCO_HDR_SIZE , \
. loff = 2 , \
. lsize = 1 , \
. maxlen = HCI_MAX_SCO_SIZE
# define H4_RECV_EVENT \
. type = HCI_EVENT_PKT , \
. hlen = HCI_EVENT_HDR_SIZE , \
. loff = 1 , \
. lsize = 1 , \
. maxlen = HCI_MAX_EVENT_SIZE
static inline struct sk_buff * h4_recv_buf ( struct hci_dev * hdev ,
struct sk_buff * skb ,
const unsigned char * buffer ,
int count ,
const struct h4_recv_pkt * pkts ,
int pkts_count )
{
2019-01-22 11:33:26 +03:00
/* Check for error from previous call */
if ( IS_ERR ( skb ) )
skb = NULL ;
2018-03-24 12:19:53 +03:00
while ( count ) {
int i , len ;
if ( ! skb ) {
for ( i = 0 ; i < pkts_count ; i + + ) {
if ( buffer [ 0 ] ! = ( & pkts [ i ] ) - > type )
continue ;
skb = bt_skb_alloc ( ( & pkts [ i ] ) - > maxlen ,
GFP_ATOMIC ) ;
if ( ! skb )
return ERR_PTR ( - ENOMEM ) ;
hci_skb_pkt_type ( skb ) = ( & pkts [ i ] ) - > type ;
hci_skb_expect ( skb ) = ( & pkts [ i ] ) - > hlen ;
break ;
}
/* Check for invalid packet type */
if ( ! skb )
return ERR_PTR ( - EILSEQ ) ;
count - = 1 ;
buffer + = 1 ;
}
len = min_t ( uint , hci_skb_expect ( skb ) - skb - > len , count ) ;
skb_put_data ( skb , buffer , len ) ;
count - = len ;
buffer + = len ;
/* Check for partial packet */
if ( skb - > len < hci_skb_expect ( skb ) )
continue ;
for ( i = 0 ; i < pkts_count ; i + + ) {
if ( hci_skb_pkt_type ( skb ) = = ( & pkts [ i ] ) - > type )
break ;
}
if ( i > = pkts_count ) {
kfree_skb ( skb ) ;
return ERR_PTR ( - EILSEQ ) ;
}
if ( skb - > len = = ( & pkts [ i ] ) - > hlen ) {
u16 dlen ;
switch ( ( & pkts [ i ] ) - > lsize ) {
case 0 :
/* No variable data length */
dlen = 0 ;
break ;
case 1 :
/* Single octet variable length */
dlen = skb - > data [ ( & pkts [ i ] ) - > loff ] ;
hci_skb_expect ( skb ) + = dlen ;
if ( skb_tailroom ( skb ) < dlen ) {
kfree_skb ( skb ) ;
return ERR_PTR ( - EMSGSIZE ) ;
}
break ;
case 2 :
/* Double octet variable length */
dlen = get_unaligned_le16 ( skb - > data +
( & pkts [ i ] ) - > loff ) ;
hci_skb_expect ( skb ) + = dlen ;
if ( skb_tailroom ( skb ) < dlen ) {
kfree_skb ( skb ) ;
return ERR_PTR ( - EMSGSIZE ) ;
}
break ;
default :
/* Unsupported variable length */
kfree_skb ( skb ) ;
return ERR_PTR ( - EILSEQ ) ;
}
if ( ! dlen ) {
/* No more data, complete frame */
( & pkts [ i ] ) - > recv ( hdev , skb ) ;
skb = NULL ;
}
} else {
/* Complete frame */
( & pkts [ i ] ) - > recv ( hdev , skb ) ;
skb = NULL ;
}
}
return skb ;
}