2012-05-29 14:59:01 +04:00
/*
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 .
*/
# include <net/bluetooth/bluetooth.h>
# include <net/bluetooth/hci_core.h>
# include <net/bluetooth/l2cap.h>
2013-10-11 01:54:14 +04:00
2015-07-24 12:10:16 +03:00
# include "hci_request.h"
2013-10-11 01:54:15 +04:00
# include "a2mp.h"
2013-10-11 01:54:14 +04:00
# include "amp.h"
2012-05-29 14:59:01 +04:00
2015-02-15 00:36:03 +03:00
# define A2MP_FEAT_EXT 0x8000
2012-09-27 18:26:07 +04:00
/* Global AMP Manager list */
2015-02-15 00:36:04 +03:00
static LIST_HEAD ( amp_mgr_list ) ;
static DEFINE_MUTEX ( amp_mgr_list_lock ) ;
2012-09-27 18:26:07 +04:00
2012-05-29 14:59:03 +04:00
/* A2MP build & send command helper functions */
static struct a2mp_cmd * __a2mp_build ( u8 code , u8 ident , u16 len , void * data )
{
struct a2mp_cmd * cmd ;
int plen ;
plen = sizeof ( * cmd ) + len ;
cmd = kzalloc ( plen , GFP_KERNEL ) ;
if ( ! cmd )
return NULL ;
cmd - > code = code ;
cmd - > ident = ident ;
cmd - > len = cpu_to_le16 ( len ) ;
memcpy ( cmd - > data , data , len ) ;
return cmd ;
}
2015-02-15 00:36:06 +03:00
static void a2mp_send ( struct amp_mgr * mgr , u8 code , u8 ident , u16 len , void * data )
2012-05-29 14:59:03 +04:00
{
struct l2cap_chan * chan = mgr - > a2mp_chan ;
struct a2mp_cmd * cmd ;
u16 total_len = len + sizeof ( * cmd ) ;
struct kvec iv ;
struct msghdr msg ;
cmd = __a2mp_build ( code , ident , len , data ) ;
if ( ! cmd )
return ;
iv . iov_base = cmd ;
iv . iov_len = total_len ;
memset ( & msg , 0 , sizeof ( msg ) ) ;
2014-11-25 01:07:38 +03:00
iov_iter_kvec ( & msg . msg_iter , WRITE | ITER_KVEC , & iv , 1 , total_len ) ;
2012-05-29 14:59:03 +04:00
2014-06-05 17:22:51 +04:00
l2cap_chan_send ( chan , & msg , total_len ) ;
2012-05-29 14:59:03 +04:00
kfree ( cmd ) ;
}
2015-02-15 00:36:07 +03:00
static u8 __next_ident ( struct amp_mgr * mgr )
2012-09-27 18:26:10 +04:00
{
if ( + + mgr - > ident = = 0 )
mgr - > ident = 1 ;
return mgr - > ident ;
}
2015-02-15 00:36:05 +03:00
static struct amp_mgr * amp_mgr_lookup_by_state ( u8 state )
{
struct amp_mgr * mgr ;
mutex_lock ( & amp_mgr_list_lock ) ;
list_for_each_entry ( mgr , & amp_mgr_list , list ) {
if ( test_and_clear_bit ( state , & mgr - > state ) ) {
amp_mgr_get ( mgr ) ;
mutex_unlock ( & amp_mgr_list_lock ) ;
return mgr ;
}
}
mutex_unlock ( & amp_mgr_list_lock ) ;
return NULL ;
}
2012-05-29 14:59:09 +04:00
/* hci_dev_list shall be locked */
2013-10-05 22:47:47 +04:00
static void __a2mp_add_cl ( struct amp_mgr * mgr , struct a2mp_cl * cl )
2012-05-29 14:59:09 +04:00
{
struct hci_dev * hdev ;
2013-10-06 13:08:35 +04:00
int i = 1 ;
2012-05-29 14:59:09 +04:00
2013-10-05 22:47:46 +04:00
cl [ 0 ] . id = AMP_ID_BREDR ;
cl [ 0 ] . type = AMP_TYPE_BREDR ;
cl [ 0 ] . status = AMP_STATUS_BLUETOOTH_ONLY ;
2012-05-29 14:59:09 +04:00
list_for_each_entry ( hdev , & hci_dev_list , list ) {
2013-10-06 13:08:35 +04:00
if ( hdev - > dev_type = = HCI_AMP ) {
cl [ i ] . id = hdev - > id ;
cl [ i ] . type = hdev - > amp_type ;
2013-10-07 11:58:34 +04:00
if ( test_bit ( HCI_UP , & hdev - > flags ) )
cl [ i ] . status = hdev - > amp_status ;
else
cl [ i ] . status = AMP_STATUS_POWERED_DOWN ;
2013-10-06 13:08:35 +04:00
i + + ;
}
2012-05-29 14:59:09 +04:00
}
}
2012-05-29 14:59:08 +04:00
/* Processing A2MP messages */
static int a2mp_command_rej ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_cmd_rej * rej = ( void * ) skb - > data ;
if ( le16_to_cpu ( hdr - > len ) < sizeof ( * rej ) )
return - EINVAL ;
BT_DBG ( " ident %d reason %d " , hdr - > ident , le16_to_cpu ( rej - > reason ) ) ;
skb_pull ( skb , sizeof ( * rej ) ) ;
return 0 ;
}
2012-05-29 14:59:09 +04:00
static int a2mp_discover_req ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_discov_req * req = ( void * ) skb - > data ;
u16 len = le16_to_cpu ( hdr - > len ) ;
struct a2mp_discov_rsp * rsp ;
u16 ext_feat ;
u8 num_ctrl ;
2013-10-05 22:47:41 +04:00
struct hci_dev * hdev ;
2012-05-29 14:59:09 +04:00
if ( len < sizeof ( * req ) )
return - EINVAL ;
skb_pull ( skb , sizeof ( * req ) ) ;
ext_feat = le16_to_cpu ( req - > ext_feat ) ;
BT_DBG ( " mtu %d efm 0x%4.4x " , le16_to_cpu ( req - > mtu ) , ext_feat ) ;
/* check that packet is not broken for now */
while ( ext_feat & A2MP_FEAT_EXT ) {
if ( len < sizeof ( ext_feat ) )
return - EINVAL ;
ext_feat = get_unaligned_le16 ( skb - > data ) ;
BT_DBG ( " efm 0x%4.4x " , ext_feat ) ;
len - = sizeof ( ext_feat ) ;
skb_pull ( skb , sizeof ( ext_feat ) ) ;
}
read_lock ( & hci_dev_list_lock ) ;
2013-10-05 22:47:41 +04:00
/* at minimum the BR/EDR needs to be listed */
num_ctrl = 1 ;
list_for_each_entry ( hdev , & hci_dev_list , list ) {
if ( hdev - > dev_type = = HCI_AMP )
num_ctrl + + ;
}
2012-05-29 14:59:09 +04:00
len = num_ctrl * sizeof ( struct a2mp_cl ) + sizeof ( * rsp ) ;
rsp = kmalloc ( len , GFP_ATOMIC ) ;
if ( ! rsp ) {
read_unlock ( & hci_dev_list_lock ) ;
return - ENOMEM ;
}
2014-03-12 21:52:35 +04:00
rsp - > mtu = cpu_to_le16 ( L2CAP_A2MP_DEFAULT_MTU ) ;
2012-05-29 14:59:09 +04:00
rsp - > ext_feat = 0 ;
2013-10-05 22:47:47 +04:00
__a2mp_add_cl ( mgr , rsp - > cl ) ;
2012-05-29 14:59:09 +04:00
read_unlock ( & hci_dev_list_lock ) ;
a2mp_send ( mgr , A2MP_DISCOVER_RSP , hdr - > ident , len , rsp ) ;
kfree ( rsp ) ;
return 0 ;
}
2012-09-27 18:26:10 +04:00
static int a2mp_discover_rsp ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_discov_rsp * rsp = ( void * ) skb - > data ;
u16 len = le16_to_cpu ( hdr - > len ) ;
struct a2mp_cl * cl ;
u16 ext_feat ;
2012-09-27 18:26:21 +04:00
bool found = false ;
2012-09-27 18:26:10 +04:00
if ( len < sizeof ( * rsp ) )
return - EINVAL ;
len - = sizeof ( * rsp ) ;
skb_pull ( skb , sizeof ( * rsp ) ) ;
ext_feat = le16_to_cpu ( rsp - > ext_feat ) ;
BT_DBG ( " mtu %d efm 0x%4.4x " , le16_to_cpu ( rsp - > mtu ) , ext_feat ) ;
/* check that packet is not broken for now */
while ( ext_feat & A2MP_FEAT_EXT ) {
if ( len < sizeof ( ext_feat ) )
return - EINVAL ;
ext_feat = get_unaligned_le16 ( skb - > data ) ;
BT_DBG ( " efm 0x%4.4x " , ext_feat ) ;
len - = sizeof ( ext_feat ) ;
skb_pull ( skb , sizeof ( ext_feat ) ) ;
}
cl = ( void * ) skb - > data ;
while ( len > = sizeof ( * cl ) ) {
BT_DBG ( " Remote AMP id %d type %d status %d " , cl - > id , cl - > type ,
cl - > status ) ;
2013-10-06 00:57:53 +04:00
if ( cl - > id ! = AMP_ID_BREDR & & cl - > type ! = AMP_TYPE_BREDR ) {
2012-09-27 18:26:10 +04:00
struct a2mp_info_req req ;
2012-09-27 18:26:21 +04:00
found = true ;
2012-09-27 18:26:10 +04:00
req . id = cl - > id ;
a2mp_send ( mgr , A2MP_GETINFO_REQ , __next_ident ( mgr ) ,
sizeof ( req ) , & req ) ;
}
len - = sizeof ( * cl ) ;
cl = ( void * ) skb_pull ( skb , sizeof ( * cl ) ) ;
}
2012-09-27 18:26:21 +04:00
/* Fall back to L2CAP init sequence */
if ( ! found ) {
struct l2cap_conn * conn = mgr - > l2cap_conn ;
struct l2cap_chan * chan ;
mutex_lock ( & conn - > chan_lock ) ;
list_for_each_entry ( chan , & conn - > chan_l , list ) {
BT_DBG ( " chan %p state %s " , chan ,
state_to_string ( chan - > state ) ) ;
2014-01-24 12:35:40 +04:00
if ( chan - > scid = = L2CAP_CID_A2MP )
2012-09-27 18:26:21 +04:00
continue ;
l2cap_chan_lock ( chan ) ;
if ( chan - > state = = BT_CONNECT )
l2cap_send_conn_req ( chan ) ;
l2cap_chan_unlock ( chan ) ;
}
mutex_unlock ( & conn - > chan_lock ) ;
}
2012-09-27 18:26:10 +04:00
return 0 ;
}
2012-05-29 14:59:10 +04:00
static int a2mp_change_notify ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_cl * cl = ( void * ) skb - > data ;
while ( skb - > len > = sizeof ( * cl ) ) {
BT_DBG ( " Controller id %d type %d status %d " , cl - > id , cl - > type ,
cl - > status ) ;
cl = ( struct a2mp_cl * ) skb_pull ( skb , sizeof ( * cl ) ) ;
}
/* TODO send A2MP_CHANGE_RSP */
return 0 ;
}
2015-07-24 12:10:16 +03:00
static void read_local_amp_info_complete ( struct hci_dev * hdev , u8 status ,
u16 opcode )
{
BT_DBG ( " %s status 0x%2.2x " , hdev - > name , status ) ;
a2mp_send_getinfo_rsp ( hdev ) ;
}
2012-05-29 14:59:11 +04:00
static int a2mp_getinfo_req ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_info_req * req = ( void * ) skb - > data ;
struct hci_dev * hdev ;
2015-07-24 12:10:16 +03:00
struct hci_request hreq ;
int err = 0 ;
2012-05-29 14:59:11 +04:00
if ( le16_to_cpu ( hdr - > len ) < sizeof ( * req ) )
return - EINVAL ;
BT_DBG ( " id %d " , req - > id ) ;
hdev = hci_dev_get ( req - > id ) ;
2012-09-28 15:28:50 +04:00
if ( ! hdev | | hdev - > dev_type ! = HCI_AMP ) {
2012-09-27 18:26:08 +04:00
struct a2mp_info_rsp rsp ;
rsp . id = req - > id ;
rsp . status = A2MP_STATUS_INVALID_CTRL_ID ;
a2mp_send ( mgr , A2MP_GETINFO_RSP , hdr - > ident , sizeof ( rsp ) ,
& rsp ) ;
2012-05-29 14:59:11 +04:00
2012-09-28 15:28:50 +04:00
goto done ;
2012-09-27 18:26:08 +04:00
}
2012-05-29 14:59:11 +04:00
2012-12-07 16:59:08 +04:00
set_bit ( READ_LOC_AMP_INFO , & mgr - > state ) ;
2015-07-24 12:10:16 +03:00
hci_req_init ( & hreq , hdev ) ;
hci_req_add ( & hreq , HCI_OP_READ_LOCAL_AMP_INFO , 0 , NULL ) ;
err = hci_req_run ( & hreq , read_local_amp_info_complete ) ;
if ( err < 0 )
a2mp_send_getinfo_rsp ( hdev ) ;
2012-09-28 15:28:50 +04:00
done :
if ( hdev )
hci_dev_put ( hdev ) ;
2012-05-29 14:59:11 +04:00
skb_pull ( skb , sizeof ( * req ) ) ;
return 0 ;
}
2012-09-27 18:26:14 +04:00
static int a2mp_getinfo_rsp ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_info_rsp * rsp = ( struct a2mp_info_rsp * ) skb - > data ;
struct a2mp_amp_assoc_req req ;
struct amp_ctrl * ctrl ;
if ( le16_to_cpu ( hdr - > len ) < sizeof ( * rsp ) )
return - EINVAL ;
BT_DBG ( " id %d status 0x%2.2x " , rsp - > id , rsp - > status ) ;
if ( rsp - > status )
return - EINVAL ;
2012-10-05 17:56:55 +04:00
ctrl = amp_ctrl_add ( mgr , rsp - > id ) ;
2012-09-27 18:26:14 +04:00
if ( ! ctrl )
return - ENOMEM ;
req . id = rsp - > id ;
a2mp_send ( mgr , A2MP_GETAMPASSOC_REQ , __next_ident ( mgr ) , sizeof ( req ) ,
& req ) ;
skb_pull ( skb , sizeof ( * rsp ) ) ;
return 0 ;
}
2012-05-29 14:59:12 +04:00
static int a2mp_getampassoc_req ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_amp_assoc_req * req = ( void * ) skb - > data ;
struct hci_dev * hdev ;
2012-09-27 18:26:09 +04:00
struct amp_mgr * tmp ;
2012-05-29 14:59:12 +04:00
if ( le16_to_cpu ( hdr - > len ) < sizeof ( * req ) )
return - EINVAL ;
BT_DBG ( " id %d " , req - > id ) ;
2012-09-27 18:26:09 +04:00
/* Make sure that other request is not processed */
tmp = amp_mgr_lookup_by_state ( READ_LOC_AMP_ASSOC ) ;
2012-05-29 14:59:12 +04:00
hdev = hci_dev_get ( req - > id ) ;
2013-10-05 22:47:43 +04:00
if ( ! hdev | | hdev - > amp_type = = AMP_TYPE_BREDR | | tmp ) {
2012-05-29 14:59:12 +04:00
struct a2mp_amp_assoc_rsp rsp ;
rsp . id = req - > id ;
2012-09-27 18:26:09 +04:00
if ( tmp ) {
rsp . status = A2MP_STATUS_COLLISION_OCCURED ;
amp_mgr_put ( tmp ) ;
} else {
rsp . status = A2MP_STATUS_INVALID_CTRL_ID ;
}
2012-05-29 14:59:12 +04:00
a2mp_send ( mgr , A2MP_GETAMPASSOC_RSP , hdr - > ident , sizeof ( rsp ) ,
& rsp ) ;
2012-09-27 18:26:09 +04:00
goto done ;
2012-05-29 14:59:12 +04:00
}
2012-09-27 18:26:09 +04:00
amp_read_loc_assoc ( hdev , mgr ) ;
2012-05-29 14:59:12 +04:00
2012-09-27 18:26:09 +04:00
done :
2012-05-29 14:59:12 +04:00
if ( hdev )
hci_dev_put ( hdev ) ;
skb_pull ( skb , sizeof ( * req ) ) ;
return 0 ;
}
2012-09-27 18:26:15 +04:00
static int a2mp_getampassoc_rsp ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_amp_assoc_rsp * rsp = ( void * ) skb - > data ;
u16 len = le16_to_cpu ( hdr - > len ) ;
struct hci_dev * hdev ;
struct amp_ctrl * ctrl ;
struct hci_conn * hcon ;
2012-09-28 17:55:00 +04:00
size_t assoc_len ;
2012-09-27 18:26:15 +04:00
if ( len < sizeof ( * rsp ) )
return - EINVAL ;
2012-09-28 17:55:00 +04:00
assoc_len = len - sizeof ( * rsp ) ;
BT_DBG ( " id %d status 0x%2.2x assoc len %zu " , rsp - > id , rsp - > status ,
assoc_len ) ;
2012-09-27 18:26:15 +04:00
if ( rsp - > status )
return - EINVAL ;
/* Save remote ASSOC data */
ctrl = amp_ctrl_lookup ( mgr , rsp - > id ) ;
if ( ctrl ) {
2012-09-28 17:55:00 +04:00
u8 * assoc ;
2012-09-27 18:26:15 +04:00
2013-03-17 09:16:50 +04:00
assoc = kmemdup ( rsp - > amp_assoc , assoc_len , GFP_KERNEL ) ;
2012-09-27 18:26:15 +04:00
if ( ! assoc ) {
amp_ctrl_put ( ctrl ) ;
return - ENOMEM ;
}
ctrl - > assoc = assoc ;
ctrl - > assoc_len = assoc_len ;
ctrl - > assoc_rem_len = assoc_len ;
ctrl - > assoc_len_so_far = 0 ;
amp_ctrl_put ( ctrl ) ;
}
/* Create Phys Link */
hdev = hci_dev_get ( rsp - > id ) ;
if ( ! hdev )
return - EINVAL ;
2012-10-05 17:56:56 +04:00
hcon = phylink_add ( hdev , mgr , rsp - > id , true ) ;
2012-09-27 18:26:15 +04:00
if ( ! hcon )
goto done ;
BT_DBG ( " Created hcon %p: loc:%d -> rem:%d " , hcon , hdev - > id , rsp - > id ) ;
2012-11-01 17:37:03 +04:00
mgr - > bredr_chan - > remote_amp_id = rsp - > id ;
2012-09-27 18:26:22 +04:00
2012-09-27 18:26:19 +04:00
amp_create_phylink ( hdev , mgr , hcon ) ;
2012-09-27 18:26:15 +04:00
done :
hci_dev_put ( hdev ) ;
skb_pull ( skb , len ) ;
return 0 ;
}
2012-05-29 14:59:13 +04:00
static int a2mp_createphyslink_req ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_physlink_req * req = ( void * ) skb - > data ;
struct a2mp_physlink_rsp rsp ;
struct hci_dev * hdev ;
2012-09-27 18:26:13 +04:00
struct hci_conn * hcon ;
2012-09-27 18:26:24 +04:00
struct amp_ctrl * ctrl ;
2012-05-29 14:59:13 +04:00
if ( le16_to_cpu ( hdr - > len ) < sizeof ( * req ) )
return - EINVAL ;
BT_DBG ( " local_id %d, remote_id %d " , req - > local_id , req - > remote_id ) ;
rsp . local_id = req - > remote_id ;
rsp . remote_id = req - > local_id ;
hdev = hci_dev_get ( req - > remote_id ) ;
2013-10-05 22:47:43 +04:00
if ( ! hdev | | hdev - > amp_type = = AMP_TYPE_BREDR ) {
2012-05-29 14:59:13 +04:00
rsp . status = A2MP_STATUS_INVALID_CTRL_ID ;
goto send_rsp ;
}
2012-09-27 18:26:24 +04:00
ctrl = amp_ctrl_lookup ( mgr , rsp . remote_id ) ;
if ( ! ctrl ) {
2012-10-05 17:56:55 +04:00
ctrl = amp_ctrl_add ( mgr , rsp . remote_id ) ;
2012-09-27 18:26:24 +04:00
if ( ctrl ) {
amp_ctrl_get ( ctrl ) ;
} else {
rsp . status = A2MP_STATUS_UNABLE_START_LINK_CREATION ;
goto send_rsp ;
}
}
if ( ctrl ) {
2012-09-28 17:55:00 +04:00
size_t assoc_len = le16_to_cpu ( hdr - > len ) - sizeof ( * req ) ;
u8 * assoc ;
2012-09-27 18:26:24 +04:00
2013-03-17 09:16:50 +04:00
assoc = kmemdup ( req - > amp_assoc , assoc_len , GFP_KERNEL ) ;
2012-09-27 18:26:24 +04:00
if ( ! assoc ) {
amp_ctrl_put ( ctrl ) ;
return - ENOMEM ;
}
ctrl - > assoc = assoc ;
ctrl - > assoc_len = assoc_len ;
ctrl - > assoc_rem_len = assoc_len ;
ctrl - > assoc_len_so_far = 0 ;
amp_ctrl_put ( ctrl ) ;
}
2012-10-05 17:56:56 +04:00
hcon = phylink_add ( hdev , mgr , req - > local_id , false ) ;
2012-09-27 18:26:13 +04:00
if ( hcon ) {
2012-09-27 18:26:23 +04:00
amp_accept_phylink ( hdev , mgr , hcon ) ;
2012-09-27 18:26:13 +04:00
rsp . status = A2MP_STATUS_SUCCESS ;
} else {
rsp . status = A2MP_STATUS_UNABLE_START_LINK_CREATION ;
}
2012-05-29 14:59:13 +04:00
send_rsp :
if ( hdev )
hci_dev_put ( hdev ) ;
2012-12-07 16:59:05 +04:00
/* Reply error now and success after HCI Write Remote AMP Assoc
command complete with success status
*/
if ( rsp . status ! = A2MP_STATUS_SUCCESS ) {
a2mp_send ( mgr , A2MP_CREATEPHYSLINK_RSP , hdr - > ident ,
sizeof ( rsp ) , & rsp ) ;
} else {
2012-12-07 16:59:08 +04:00
set_bit ( WRITE_REMOTE_AMP_ASSOC , & mgr - > state ) ;
2012-12-07 16:59:05 +04:00
mgr - > ident = hdr - > ident ;
}
2012-05-29 14:59:13 +04:00
skb_pull ( skb , le16_to_cpu ( hdr - > len ) ) ;
return 0 ;
}
2012-05-29 14:59:14 +04:00
static int a2mp_discphyslink_req ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
struct a2mp_physlink_req * req = ( void * ) skb - > data ;
struct a2mp_physlink_rsp rsp ;
struct hci_dev * hdev ;
2012-09-27 18:26:13 +04:00
struct hci_conn * hcon ;
2012-05-29 14:59:14 +04:00
if ( le16_to_cpu ( hdr - > len ) < sizeof ( * req ) )
return - EINVAL ;
BT_DBG ( " local_id %d remote_id %d " , req - > local_id , req - > remote_id ) ;
rsp . local_id = req - > remote_id ;
rsp . remote_id = req - > local_id ;
rsp . status = A2MP_STATUS_SUCCESS ;
2012-09-27 18:26:13 +04:00
hdev = hci_dev_get ( req - > remote_id ) ;
2012-05-29 14:59:14 +04:00
if ( ! hdev ) {
rsp . status = A2MP_STATUS_INVALID_CTRL_ID ;
goto send_rsp ;
}
2013-10-13 13:23:38 +04:00
hcon = hci_conn_hash_lookup_ba ( hdev , AMP_LINK ,
& mgr - > l2cap_conn - > hcon - > dst ) ;
2012-09-27 18:26:13 +04:00
if ( ! hcon ) {
BT_ERR ( " No phys link exist " ) ;
rsp . status = A2MP_STATUS_NO_PHYSICAL_LINK_EXISTS ;
goto clean ;
}
2012-05-29 14:59:14 +04:00
/* TODO Disconnect Phys Link here */
2012-09-27 18:26:13 +04:00
clean :
2012-05-29 14:59:14 +04:00
hci_dev_put ( hdev ) ;
send_rsp :
a2mp_send ( mgr , A2MP_DISCONNPHYSLINK_RSP , hdr - > ident , sizeof ( rsp ) , & rsp ) ;
skb_pull ( skb , sizeof ( * req ) ) ;
return 0 ;
}
2012-05-29 14:59:15 +04:00
static inline int a2mp_cmd_rsp ( struct amp_mgr * mgr , struct sk_buff * skb ,
struct a2mp_cmd * hdr )
{
2012-07-19 18:03:41 +04:00
BT_DBG ( " ident %d code 0x%2.2x " , hdr - > ident , hdr - > code ) ;
2012-05-29 14:59:15 +04:00
skb_pull ( skb , le16_to_cpu ( hdr - > len ) ) ;
return 0 ;
}
2012-05-29 14:59:07 +04:00
/* Handle A2MP signalling */
static int a2mp_chan_recv_cb ( struct l2cap_chan * chan , struct sk_buff * skb )
{
2012-07-19 18:03:46 +04:00
struct a2mp_cmd * hdr ;
2012-05-29 14:59:07 +04:00
struct amp_mgr * mgr = chan - > data ;
int err = 0 ;
amp_mgr_get ( mgr ) ;
while ( skb - > len > = sizeof ( * hdr ) ) {
2012-07-19 18:03:46 +04:00
u16 len ;
hdr = ( void * ) skb - > data ;
len = le16_to_cpu ( hdr - > len ) ;
2012-05-29 14:59:07 +04:00
2012-07-19 18:03:41 +04:00
BT_DBG ( " code 0x%2.2x id %d len %u " , hdr - > code , hdr - > ident , len ) ;
2012-05-29 14:59:07 +04:00
skb_pull ( skb , sizeof ( * hdr ) ) ;
if ( len > skb - > len | | ! hdr - > ident ) {
err = - EINVAL ;
break ;
}
mgr - > ident = hdr - > ident ;
switch ( hdr - > code ) {
case A2MP_COMMAND_REJ :
2012-05-29 14:59:08 +04:00
a2mp_command_rej ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
case A2MP_DISCOVER_REQ :
2012-05-29 14:59:09 +04:00
err = a2mp_discover_req ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
case A2MP_CHANGE_NOTIFY :
2012-05-29 14:59:10 +04:00
err = a2mp_change_notify ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
case A2MP_GETINFO_REQ :
2012-05-29 14:59:11 +04:00
err = a2mp_getinfo_req ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
case A2MP_GETAMPASSOC_REQ :
2012-05-29 14:59:12 +04:00
err = a2mp_getampassoc_req ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
case A2MP_CREATEPHYSLINK_REQ :
2012-05-29 14:59:13 +04:00
err = a2mp_createphyslink_req ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
case A2MP_DISCONNPHYSLINK_REQ :
2012-05-29 14:59:14 +04:00
err = a2mp_discphyslink_req ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
case A2MP_DISCOVER_RSP :
2012-09-27 18:26:10 +04:00
err = a2mp_discover_rsp ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
case A2MP_GETINFO_RSP :
2012-09-27 18:26:14 +04:00
err = a2mp_getinfo_rsp ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
case A2MP_GETAMPASSOC_RSP :
2012-09-27 18:26:15 +04:00
err = a2mp_getampassoc_rsp ( mgr , skb , hdr ) ;
break ;
case A2MP_CHANGE_RSP :
2012-05-29 14:59:07 +04:00
case A2MP_CREATEPHYSLINK_RSP :
case A2MP_DISCONNPHYSLINK_RSP :
2012-05-29 14:59:15 +04:00
err = a2mp_cmd_rsp ( mgr , skb , hdr ) ;
break ;
2012-05-29 14:59:07 +04:00
default :
BT_ERR ( " Unknown A2MP sig cmd 0x%2.2x " , hdr - > code ) ;
err = - EINVAL ;
break ;
}
}
if ( err ) {
struct a2mp_cmd_rej rej ;
2012-07-19 18:03:46 +04:00
2014-03-12 21:52:35 +04:00
rej . reason = cpu_to_le16 ( 0 ) ;
2012-07-19 18:03:46 +04:00
hdr = ( void * ) skb - > data ;
2012-05-29 14:59:07 +04:00
BT_DBG ( " Send A2MP Rej: cmd 0x%2.2x err %d " , hdr - > code , err ) ;
a2mp_send ( mgr , A2MP_COMMAND_REJ , hdr - > ident , sizeof ( rej ) ,
& rej ) ;
}
/* Always free skb and return success error code to prevent
from sending L2CAP Disconnect over A2MP channel */
kfree_skb ( skb ) ;
amp_mgr_put ( mgr ) ;
return 0 ;
}
2012-05-29 14:59:04 +04:00
static void a2mp_chan_close_cb ( struct l2cap_chan * chan )
{
2012-07-13 16:47:55 +04:00
l2cap_chan_put ( chan ) ;
2012-05-29 14:59:04 +04:00
}
2013-10-16 02:24:45 +04:00
static void a2mp_chan_state_change_cb ( struct l2cap_chan * chan , int state ,
int err )
2012-05-29 14:59:04 +04:00
{
struct amp_mgr * mgr = chan - > data ;
if ( ! mgr )
return ;
BT_DBG ( " chan %p state %s " , chan , state_to_string ( state ) ) ;
chan - > state = state ;
switch ( state ) {
case BT_CLOSED :
if ( mgr )
amp_mgr_put ( mgr ) ;
break ;
}
}
static struct sk_buff * a2mp_chan_alloc_skb_cb ( struct l2cap_chan * chan ,
2014-06-08 13:22:28 +04:00
unsigned long hdr_len ,
2012-05-29 14:59:04 +04:00
unsigned long len , int nb )
{
2012-12-22 07:22:53 +04:00
struct sk_buff * skb ;
2014-06-08 13:22:28 +04:00
skb = bt_skb_alloc ( hdr_len + len , GFP_KERNEL ) ;
2012-12-22 07:22:53 +04:00
if ( ! skb )
return ERR_PTR ( - ENOMEM ) ;
return skb ;
2012-05-29 14:59:04 +04:00
}
2014-06-08 12:05:31 +04:00
static const struct l2cap_ops a2mp_chan_ops = {
2012-05-29 14:59:01 +04:00
. name = " L2CAP A2MP channel " ,
2012-05-29 14:59:07 +04:00
. recv = a2mp_chan_recv_cb ,
2012-05-29 14:59:04 +04:00
. close = a2mp_chan_close_cb ,
. state_change = a2mp_chan_state_change_cb ,
. alloc_skb = a2mp_chan_alloc_skb_cb ,
/* Not implemented for A2MP */
2012-05-29 20:19:26 +04:00
. new_connection = l2cap_chan_no_new_connection ,
. teardown = l2cap_chan_no_teardown ,
. ready = l2cap_chan_no_ready ,
2012-10-12 15:35:24 +04:00
. defer = l2cap_chan_no_defer ,
2013-10-16 03:47:11 +04:00
. resume = l2cap_chan_no_resume ,
2013-10-16 02:24:48 +04:00
. set_shutdown = l2cap_chan_no_set_shutdown ,
2013-10-16 02:24:47 +04:00
. get_sndtimeo = l2cap_chan_no_get_sndtimeo ,
2012-05-29 14:59:01 +04:00
} ;
2012-09-27 18:26:16 +04:00
static struct l2cap_chan * a2mp_chan_open ( struct l2cap_conn * conn , bool locked )
2012-05-29 14:59:01 +04:00
{
struct l2cap_chan * chan ;
int err ;
chan = l2cap_chan_create ( ) ;
if ( ! chan )
return NULL ;
BT_DBG ( " chan %p " , chan ) ;
2014-01-24 12:35:40 +04:00
chan - > chan_type = L2CAP_CHAN_FIXED ;
chan - > scid = L2CAP_CID_A2MP ;
chan - > dcid = L2CAP_CID_A2MP ;
chan - > omtu = L2CAP_A2MP_DEFAULT_MTU ;
chan - > imtu = L2CAP_A2MP_DEFAULT_MTU ;
2012-05-29 14:59:01 +04:00
chan - > flush_to = L2CAP_DEFAULT_FLUSH_TO ;
chan - > ops = & a2mp_chan_ops ;
l2cap_chan_set_defaults ( chan ) ;
chan - > remote_max_tx = chan - > max_tx ;
chan - > remote_tx_win = chan - > tx_win ;
chan - > retrans_timeout = L2CAP_DEFAULT_RETRANS_TO ;
chan - > monitor_timeout = L2CAP_DEFAULT_MONITOR_TO ;
skb_queue_head_init ( & chan - > tx_q ) ;
chan - > mode = L2CAP_MODE_ERTM ;
err = l2cap_ertm_init ( chan ) ;
if ( err < 0 ) {
l2cap_chan_del ( chan , 0 ) ;
return NULL ;
}
chan - > conf_state = 0 ;
2012-09-27 18:26:16 +04:00
if ( locked )
__l2cap_chan_add ( conn , chan ) ;
else
l2cap_chan_add ( conn , chan ) ;
2012-05-29 14:59:01 +04:00
chan - > remote_mps = chan - > omtu ;
chan - > mps = chan - > omtu ;
chan - > state = BT_CONNECTED ;
return chan ;
}
2012-05-29 14:59:02 +04:00
/* AMP Manager functions */
2012-10-18 14:16:19 +04:00
struct amp_mgr * amp_mgr_get ( struct amp_mgr * mgr )
2012-05-29 14:59:02 +04:00
{
2012-07-11 15:43:35 +04:00
BT_DBG ( " mgr %p orig refcnt %d " , mgr , atomic_read ( & mgr - > kref . refcount ) ) ;
2012-05-29 14:59:02 +04:00
kref_get ( & mgr - > kref ) ;
2012-10-18 14:16:19 +04:00
return mgr ;
2012-05-29 14:59:02 +04:00
}
static void amp_mgr_destroy ( struct kref * kref )
{
struct amp_mgr * mgr = container_of ( kref , struct amp_mgr , kref ) ;
BT_DBG ( " mgr %p " , mgr ) ;
2012-09-27 18:26:07 +04:00
mutex_lock ( & amp_mgr_list_lock ) ;
list_del ( & mgr - > list ) ;
mutex_unlock ( & amp_mgr_list_lock ) ;
2012-09-27 18:26:12 +04:00
amp_ctrl_list_flush ( mgr ) ;
2012-05-29 14:59:02 +04:00
kfree ( mgr ) ;
}
int amp_mgr_put ( struct amp_mgr * mgr )
{
2012-07-11 15:43:35 +04:00
BT_DBG ( " mgr %p orig refcnt %d " , mgr , atomic_read ( & mgr - > kref . refcount ) ) ;
2012-05-29 14:59:02 +04:00
return kref_put ( & mgr - > kref , & amp_mgr_destroy ) ;
}
2012-09-27 18:26:16 +04:00
static struct amp_mgr * amp_mgr_create ( struct l2cap_conn * conn , bool locked )
2012-05-29 14:59:02 +04:00
{
struct amp_mgr * mgr ;
struct l2cap_chan * chan ;
mgr = kzalloc ( sizeof ( * mgr ) , GFP_KERNEL ) ;
if ( ! mgr )
return NULL ;
BT_DBG ( " conn %p mgr %p " , conn , mgr ) ;
mgr - > l2cap_conn = conn ;
2012-09-27 18:26:16 +04:00
chan = a2mp_chan_open ( conn , locked ) ;
2012-05-29 14:59:02 +04:00
if ( ! chan ) {
kfree ( mgr ) ;
return NULL ;
}
mgr - > a2mp_chan = chan ;
chan - > data = mgr ;
conn - > hcon - > amp_mgr = mgr ;
kref_init ( & mgr - > kref ) ;
2012-09-27 18:26:12 +04:00
/* Remote AMP ctrl list initialization */
INIT_LIST_HEAD ( & mgr - > amp_ctrls ) ;
mutex_init ( & mgr - > amp_ctrls_lock ) ;
2012-09-27 18:26:07 +04:00
mutex_lock ( & amp_mgr_list_lock ) ;
list_add ( & mgr - > list , & amp_mgr_list ) ;
mutex_unlock ( & amp_mgr_list_lock ) ;
2012-05-29 14:59:02 +04:00
return mgr ;
}
2012-05-29 14:59:17 +04:00
struct l2cap_chan * a2mp_channel_create ( struct l2cap_conn * conn ,
struct sk_buff * skb )
{
struct amp_mgr * mgr ;
2013-10-16 12:37:00 +04:00
if ( conn - > hcon - > type ! = ACL_LINK )
return NULL ;
2012-09-27 18:26:16 +04:00
mgr = amp_mgr_create ( conn , false ) ;
2012-05-29 14:59:17 +04:00
if ( ! mgr ) {
BT_ERR ( " Could not create AMP manager " ) ;
return NULL ;
}
BT_DBG ( " mgr: %p chan %p " , mgr , mgr - > a2mp_chan ) ;
return mgr - > a2mp_chan ;
}
2012-09-27 18:26:07 +04:00
2012-09-27 18:26:08 +04:00
void a2mp_send_getinfo_rsp ( struct hci_dev * hdev )
{
struct amp_mgr * mgr ;
struct a2mp_info_rsp rsp ;
mgr = amp_mgr_lookup_by_state ( READ_LOC_AMP_INFO ) ;
if ( ! mgr )
return ;
BT_DBG ( " %s mgr %p " , hdev - > name , mgr ) ;
rsp . id = hdev - > id ;
rsp . status = A2MP_STATUS_INVALID_CTRL_ID ;
2013-10-05 22:47:43 +04:00
if ( hdev - > amp_type ! = AMP_TYPE_BREDR ) {
2012-09-27 18:26:08 +04:00
rsp . status = 0 ;
rsp . total_bw = cpu_to_le32 ( hdev - > amp_total_bw ) ;
rsp . max_bw = cpu_to_le32 ( hdev - > amp_max_bw ) ;
rsp . min_latency = cpu_to_le32 ( hdev - > amp_min_latency ) ;
rsp . pal_cap = cpu_to_le16 ( hdev - > amp_pal_cap ) ;
rsp . assoc_size = cpu_to_le16 ( hdev - > amp_assoc_size ) ;
}
a2mp_send ( mgr , A2MP_GETINFO_RSP , mgr - > ident , sizeof ( rsp ) , & rsp ) ;
amp_mgr_put ( mgr ) ;
}
2012-09-27 18:26:09 +04:00
void a2mp_send_getampassoc_rsp ( struct hci_dev * hdev , u8 status )
{
struct amp_mgr * mgr ;
struct amp_assoc * loc_assoc = & hdev - > loc_assoc ;
struct a2mp_amp_assoc_rsp * rsp ;
size_t len ;
mgr = amp_mgr_lookup_by_state ( READ_LOC_AMP_ASSOC ) ;
if ( ! mgr )
return ;
BT_DBG ( " %s mgr %p " , hdev - > name , mgr ) ;
len = sizeof ( struct a2mp_amp_assoc_rsp ) + loc_assoc - > len ;
rsp = kzalloc ( len , GFP_KERNEL ) ;
if ( ! rsp ) {
amp_mgr_put ( mgr ) ;
return ;
}
rsp - > id = hdev - > id ;
if ( status ) {
rsp - > status = A2MP_STATUS_INVALID_CTRL_ID ;
} else {
rsp - > status = A2MP_STATUS_SUCCESS ;
memcpy ( rsp - > amp_assoc , loc_assoc - > data , loc_assoc - > len ) ;
}
a2mp_send ( mgr , A2MP_GETAMPASSOC_RSP , mgr - > ident , len , rsp ) ;
amp_mgr_put ( mgr ) ;
kfree ( rsp ) ;
}
2012-09-27 18:26:16 +04:00
2012-09-27 18:26:22 +04:00
void a2mp_send_create_phy_link_req ( struct hci_dev * hdev , u8 status )
{
struct amp_mgr * mgr ;
struct amp_assoc * loc_assoc = & hdev - > loc_assoc ;
struct a2mp_physlink_req * req ;
struct l2cap_chan * bredr_chan ;
size_t len ;
mgr = amp_mgr_lookup_by_state ( READ_LOC_AMP_ASSOC_FINAL ) ;
if ( ! mgr )
return ;
len = sizeof ( * req ) + loc_assoc - > len ;
BT_DBG ( " %s mgr %p assoc_len %zu " , hdev - > name , mgr , len ) ;
req = kzalloc ( len , GFP_KERNEL ) ;
if ( ! req ) {
amp_mgr_put ( mgr ) ;
return ;
}
bredr_chan = mgr - > bredr_chan ;
if ( ! bredr_chan )
goto clean ;
req - > local_id = hdev - > id ;
2012-11-01 17:37:03 +04:00
req - > remote_id = bredr_chan - > remote_amp_id ;
2012-09-27 18:26:22 +04:00
memcpy ( req - > amp_assoc , loc_assoc - > data , loc_assoc - > len ) ;
a2mp_send ( mgr , A2MP_CREATEPHYSLINK_REQ , __next_ident ( mgr ) , len , req ) ;
clean :
amp_mgr_put ( mgr ) ;
kfree ( req ) ;
}
2012-12-07 16:59:05 +04:00
void a2mp_send_create_phy_link_rsp ( struct hci_dev * hdev , u8 status )
{
struct amp_mgr * mgr ;
struct a2mp_physlink_rsp rsp ;
struct hci_conn * hs_hcon ;
mgr = amp_mgr_lookup_by_state ( WRITE_REMOTE_AMP_ASSOC ) ;
if ( ! mgr )
return ;
hs_hcon = hci_conn_hash_lookup_state ( hdev , AMP_LINK , BT_CONNECT ) ;
if ( ! hs_hcon ) {
rsp . status = A2MP_STATUS_UNABLE_START_LINK_CREATION ;
} else {
rsp . remote_id = hs_hcon - > remote_id ;
rsp . status = A2MP_STATUS_SUCCESS ;
}
BT_DBG ( " %s mgr %p hs_hcon %p status %u " , hdev - > name , mgr , hs_hcon ,
status ) ;
rsp . local_id = hdev - > id ;
a2mp_send ( mgr , A2MP_CREATEPHYSLINK_RSP , mgr - > ident , sizeof ( rsp ) , & rsp ) ;
amp_mgr_put ( mgr ) ;
}
2012-09-27 18:26:16 +04:00
void a2mp_discover_amp ( struct l2cap_chan * chan )
{
struct l2cap_conn * conn = chan - > conn ;
struct amp_mgr * mgr = conn - > hcon - > amp_mgr ;
struct a2mp_discov_req req ;
BT_DBG ( " chan %p conn %p mgr %p " , chan , conn , mgr ) ;
if ( ! mgr ) {
mgr = amp_mgr_create ( conn , true ) ;
if ( ! mgr )
return ;
}
mgr - > bredr_chan = chan ;
req . mtu = cpu_to_le16 ( L2CAP_A2MP_DEFAULT_MTU ) ;
req . ext_feat = 0 ;
a2mp_send ( mgr , A2MP_DISCOVER_REQ , 1 , sizeof ( req ) , & req ) ;
}