2015-03-17 13:48:48 +02:00
/*
BlueZ - Bluetooth protocol stack for Linux
Copyright ( C ) 2015 Intel Corporation
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 .
*/
2016-08-27 20:23:41 +02:00
# include <asm/unaligned.h>
2015-03-17 13:48:48 +02:00
# include <net/bluetooth/bluetooth.h>
# include <net/bluetooth/hci_core.h>
2016-08-27 20:23:41 +02:00
# include <net/bluetooth/hci_mon.h>
2015-03-17 13:48:48 +02:00
# include <net/bluetooth/mgmt.h>
# include "mgmt_util.h"
2016-08-27 20:23:41 +02:00
static struct sk_buff * create_monitor_ctrl_event ( __le16 index , u32 cookie ,
u16 opcode , u16 len , void * buf )
{
struct hci_mon_hdr * hdr ;
struct sk_buff * skb ;
skb = bt_skb_alloc ( 6 + len , GFP_ATOMIC ) ;
if ( ! skb )
return NULL ;
put_unaligned_le32 ( cookie , skb_put ( skb , 4 ) ) ;
put_unaligned_le16 ( opcode , skb_put ( skb , 2 ) ) ;
if ( buf )
memcpy ( skb_put ( skb , len ) , buf , len ) ;
__net_timestamp ( skb ) ;
hdr = ( void * ) skb_push ( skb , HCI_MON_HDR_SIZE ) ;
hdr - > opcode = cpu_to_le16 ( HCI_MON_CTRL_EVENT ) ;
hdr - > index = index ;
hdr - > len = cpu_to_le16 ( skb - > len - HCI_MON_HDR_SIZE ) ;
return skb ;
}
2015-03-17 13:48:48 +02:00
int mgmt_send_event ( u16 event , struct hci_dev * hdev , unsigned short channel ,
void * data , u16 data_len , int flag , struct sock * skip_sk )
{
struct sk_buff * skb ;
struct mgmt_hdr * hdr ;
skb = alloc_skb ( sizeof ( * hdr ) + data_len , GFP_KERNEL ) ;
if ( ! skb )
return - ENOMEM ;
hdr = ( void * ) skb_put ( skb , sizeof ( * hdr ) ) ;
hdr - > opcode = cpu_to_le16 ( event ) ;
if ( hdev )
hdr - > index = cpu_to_le16 ( hdev - > id ) ;
else
hdr - > index = cpu_to_le16 ( MGMT_INDEX_NONE ) ;
hdr - > len = cpu_to_le16 ( data_len ) ;
if ( data )
memcpy ( skb_put ( skb , data_len ) , data , data_len ) ;
/* Time stamp */
__net_timestamp ( skb ) ;
hci_send_to_channel ( channel , skb , flag , skip_sk ) ;
2016-08-27 20:23:41 +02:00
if ( channel = = HCI_CHANNEL_CONTROL )
hci_send_monitor_ctrl_event ( hdev , event , data , data_len ,
skb_get_ktime ( skb ) , flag , skip_sk ) ;
kfree_skb ( skb ) ;
2015-03-17 13:48:48 +02:00
return 0 ;
}
int mgmt_cmd_status ( struct sock * sk , u16 index , u16 cmd , u8 status )
{
2016-08-27 20:23:41 +02:00
struct sk_buff * skb , * mskb ;
2015-03-17 13:48:48 +02:00
struct mgmt_hdr * hdr ;
struct mgmt_ev_cmd_status * ev ;
int err ;
BT_DBG ( " sock %p, index %u, cmd %u, status %u " , sk , index , cmd , status ) ;
skb = alloc_skb ( sizeof ( * hdr ) + sizeof ( * ev ) , GFP_KERNEL ) ;
if ( ! skb )
return - ENOMEM ;
hdr = ( void * ) skb_put ( skb , sizeof ( * hdr ) ) ;
hdr - > opcode = cpu_to_le16 ( MGMT_EV_CMD_STATUS ) ;
hdr - > index = cpu_to_le16 ( index ) ;
hdr - > len = cpu_to_le16 ( sizeof ( * ev ) ) ;
ev = ( void * ) skb_put ( skb , sizeof ( * ev ) ) ;
ev - > status = status ;
ev - > opcode = cpu_to_le16 ( cmd ) ;
2016-08-27 20:23:41 +02:00
mskb = create_monitor_ctrl_event ( hdr - > index , hci_sock_get_cookie ( sk ) ,
MGMT_EV_CMD_STATUS , sizeof ( * ev ) , ev ) ;
if ( mskb )
skb - > tstamp = mskb - > tstamp ;
else
__net_timestamp ( skb ) ;
2015-03-17 13:48:48 +02:00
err = sock_queue_rcv_skb ( sk , skb ) ;
if ( err < 0 )
kfree_skb ( skb ) ;
2016-08-27 20:23:41 +02:00
if ( mskb ) {
hci_send_to_channel ( HCI_CHANNEL_MONITOR , mskb ,
HCI_SOCK_TRUSTED , NULL ) ;
kfree_skb ( mskb ) ;
}
2015-03-17 13:48:48 +02:00
return err ;
}
int mgmt_cmd_complete ( struct sock * sk , u16 index , u16 cmd , u8 status ,
void * rp , size_t rp_len )
{
2016-08-27 20:23:41 +02:00
struct sk_buff * skb , * mskb ;
2015-03-17 13:48:48 +02:00
struct mgmt_hdr * hdr ;
struct mgmt_ev_cmd_complete * ev ;
int err ;
BT_DBG ( " sock %p " , sk ) ;
skb = alloc_skb ( sizeof ( * hdr ) + sizeof ( * ev ) + rp_len , GFP_KERNEL ) ;
if ( ! skb )
return - ENOMEM ;
hdr = ( void * ) skb_put ( skb , sizeof ( * hdr ) ) ;
hdr - > opcode = cpu_to_le16 ( MGMT_EV_CMD_COMPLETE ) ;
hdr - > index = cpu_to_le16 ( index ) ;
hdr - > len = cpu_to_le16 ( sizeof ( * ev ) + rp_len ) ;
ev = ( void * ) skb_put ( skb , sizeof ( * ev ) + rp_len ) ;
ev - > opcode = cpu_to_le16 ( cmd ) ;
ev - > status = status ;
if ( rp )
memcpy ( ev - > data , rp , rp_len ) ;
2016-08-27 20:23:41 +02:00
mskb = create_monitor_ctrl_event ( hdr - > index , hci_sock_get_cookie ( sk ) ,
MGMT_EV_CMD_COMPLETE ,
sizeof ( * ev ) + rp_len , ev ) ;
if ( mskb )
skb - > tstamp = mskb - > tstamp ;
else
__net_timestamp ( skb ) ;
2015-03-17 13:48:48 +02:00
err = sock_queue_rcv_skb ( sk , skb ) ;
if ( err < 0 )
kfree_skb ( skb ) ;
2016-08-27 20:23:41 +02:00
if ( mskb ) {
hci_send_to_channel ( HCI_CHANNEL_MONITOR , mskb ,
HCI_SOCK_TRUSTED , NULL ) ;
kfree_skb ( mskb ) ;
}
2015-03-17 13:48:48 +02:00
return err ;
}
struct mgmt_pending_cmd * mgmt_pending_find ( unsigned short channel , u16 opcode ,
struct hci_dev * hdev )
{
struct mgmt_pending_cmd * cmd ;
list_for_each_entry ( cmd , & hdev - > mgmt_pending , list ) {
if ( hci_sock_get_channel ( cmd - > sk ) ! = channel )
continue ;
if ( cmd - > opcode = = opcode )
return cmd ;
}
return NULL ;
}
struct mgmt_pending_cmd * mgmt_pending_find_data ( unsigned short channel ,
u16 opcode ,
struct hci_dev * hdev ,
const void * data )
{
struct mgmt_pending_cmd * cmd ;
list_for_each_entry ( cmd , & hdev - > mgmt_pending , list ) {
if ( cmd - > user_data ! = data )
continue ;
if ( cmd - > opcode = = opcode )
return cmd ;
}
return NULL ;
}
void mgmt_pending_foreach ( u16 opcode , struct hci_dev * hdev ,
void ( * cb ) ( struct mgmt_pending_cmd * cmd , void * data ) ,
void * data )
{
struct mgmt_pending_cmd * cmd , * tmp ;
list_for_each_entry_safe ( cmd , tmp , & hdev - > mgmt_pending , list ) {
if ( opcode > 0 & & cmd - > opcode ! = opcode )
continue ;
cb ( cmd , data ) ;
}
}
struct mgmt_pending_cmd * mgmt_pending_add ( struct sock * sk , u16 opcode ,
struct hci_dev * hdev ,
void * data , u16 len )
{
struct mgmt_pending_cmd * cmd ;
cmd = kzalloc ( sizeof ( * cmd ) , GFP_KERNEL ) ;
if ( ! cmd )
return NULL ;
cmd - > opcode = opcode ;
cmd - > index = hdev - > id ;
cmd - > param = kmemdup ( data , len , GFP_KERNEL ) ;
if ( ! cmd - > param ) {
kfree ( cmd ) ;
return NULL ;
}
cmd - > param_len = len ;
cmd - > sk = sk ;
sock_hold ( sk ) ;
list_add ( & cmd - > list , & hdev - > mgmt_pending ) ;
return cmd ;
}
void mgmt_pending_free ( struct mgmt_pending_cmd * cmd )
{
sock_put ( cmd - > sk ) ;
kfree ( cmd - > param ) ;
kfree ( cmd ) ;
}
void mgmt_pending_remove ( struct mgmt_pending_cmd * cmd )
{
list_del ( & cmd - > list ) ;
mgmt_pending_free ( cmd ) ;
}