2008-09-17 19:34:24 +04:00
/*
* WUSB Wire Adapter : Control / Data Streaming Interface ( WUSB [ 8 ] )
* Device Connect handling
*
* Copyright ( C ) 2006 Intel Corporation
* Inaky Perez - Gonzalez < inaky . perez - gonzalez @ intel . com >
*
* 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 .
*
* 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 . , 51 Franklin Street , Fifth Floor , Boston , MA
* 02110 - 1301 , USA .
*
*
* FIXME : docs
* FIXME : this file needs to be broken up , it ' s grown too big
*
*
* WUSB1 .0 [ 7.1 , 7.5 .1 , ]
*
* WUSB device connection is kind of messy . Some background :
*
* When a device wants to connect it scans the UWB radio channels
* looking for a WUSB Channel ; a WUSB channel is defined by MMCs
* ( Micro Managed Commands or something like that ) [ see
* Design - overview for more on this ] .
*
* So , device scans the radio , finds MMCs and thus a host and checks
* when the next DNTS is . It sends a Device Notification Connect
* ( DN_Connect ) ; the host picks it up ( through nep . c and notif . c , ends
* up in wusb_devconnect_ack ( ) , which creates a wusb_dev structure in
* wusbhc - > port [ port_number ] . wusb_dev ) , assigns an unauth address
* to the device ( this means from 0x80 to 0xfe ) and sends , in the MMC
* a Connect Ack Information Element ( ConnAck IE ) .
*
* So now the device now has a WUSB address . From now on , we use
* that to talk to it in the RPipes .
*
* ASSUMPTIONS :
*
* - We use the the as device address the port number where it is
* connected ( port 0 doesn ' t exist ) . For unauth , it is 128 + that .
*
* ROADMAP :
*
* This file contains the logic for doing that - - entry points :
*
* wusb_devconnect_ack ( ) Ack a device until _acked ( ) called .
* Called by notif . c : wusb_handle_dn_connect ( )
* when a DN_Connect is received .
*
* wusb_devconnect_acked ( ) Ack done , release resources .
*
* wusb_handle_dn_alive ( ) Called by notif . c : wusb_handle_dn ( )
* for processing a DN_Alive pong from a device .
*
* wusb_handle_dn_disconnect ( ) Called by notif . c : wusb_handle_dn ( ) to
* process a disconenct request from a
* device .
*
* __wusb_dev_disable ( ) Called by rh . c : wusbhc_rh_clear_port_feat ( ) when
* disabling a port .
*
* wusb_devconnect_create ( ) Called when creating the host by
* lc . c : wusbhc_create ( ) .
*
* wusb_devconnect_destroy ( ) Cleanup called removing the host . Called
* by lc . c : wusbhc_destroy ( ) .
*
* Each Wireless USB host maintains a list of DN_Connect requests
* ( actually we maintain a list of pending Connect Acks , the
* wusbhc - > ca_list ) .
*
* LIFE CYCLE OF port - > wusb_dev
*
* Before the @ wusbhc structure put ( ) s the reference it owns for
* port - > wusb_dev [ and clean the wusb_dev pointer ] , it needs to
* lock @ wusbhc - > mutex .
*/
# include <linux/jiffies.h>
# include <linux/ctype.h>
# include <linux/workqueue.h>
# include "wusbhc.h"
static void wusbhc_devconnect_acked_work ( struct work_struct * work ) ;
static void wusb_dev_free ( struct wusb_dev * wusb_dev )
{
if ( wusb_dev ) {
kfree ( wusb_dev - > set_gtk_req ) ;
usb_free_urb ( wusb_dev - > set_gtk_urb ) ;
kfree ( wusb_dev ) ;
}
}
static struct wusb_dev * wusb_dev_alloc ( struct wusbhc * wusbhc )
{
struct wusb_dev * wusb_dev ;
struct urb * urb ;
struct usb_ctrlrequest * req ;
wusb_dev = kzalloc ( sizeof ( * wusb_dev ) , GFP_KERNEL ) ;
if ( wusb_dev = = NULL )
goto err ;
wusb_dev - > wusbhc = wusbhc ;
INIT_WORK ( & wusb_dev - > devconnect_acked_work , wusbhc_devconnect_acked_work ) ;
urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( urb = = NULL )
goto err ;
req = kmalloc ( sizeof ( struct usb_ctrlrequest ) , GFP_KERNEL ) ;
if ( req = = NULL )
goto err ;
req - > bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE ;
req - > bRequest = USB_REQ_SET_DESCRIPTOR ;
req - > wValue = cpu_to_le16 ( USB_DT_KEY < < 8 | wusbhc - > gtk_index ) ;
req - > wIndex = 0 ;
req - > wLength = cpu_to_le16 ( wusbhc - > gtk . descr . bLength ) ;
wusb_dev - > set_gtk_urb = urb ;
wusb_dev - > set_gtk_req = req ;
return wusb_dev ;
err :
wusb_dev_free ( wusb_dev ) ;
return NULL ;
}
/*
* Using the Connect - Ack list , fill out the @ wusbhc Connect - Ack WUSB IE
* properly so that it can be added to the MMC .
*
* We just get the @ wusbhc - > ca_list and fill out the first four ones or
* less ( per - spec WUSB1 .0 [ 7.5 , before T7 - 38 ) . If the ConnectAck WUSB
* IE is not allocated , we alloc it .
*
* @ wusbhc - > mutex must be taken
*/
static void wusbhc_fill_cack_ie ( struct wusbhc * wusbhc )
{
unsigned cnt ;
struct wusb_dev * dev_itr ;
struct wuie_connect_ack * cack_ie ;
cack_ie = & wusbhc - > cack_ie ;
cnt = 0 ;
list_for_each_entry ( dev_itr , & wusbhc - > cack_list , cack_node ) {
cack_ie - > blk [ cnt ] . CDID = dev_itr - > cdid ;
cack_ie - > blk [ cnt ] . bDeviceAddress = dev_itr - > addr ;
if ( + + cnt > = WUIE_ELT_MAX )
break ;
}
cack_ie - > hdr . bLength = sizeof ( cack_ie - > hdr )
+ cnt * sizeof ( cack_ie - > blk [ 0 ] ) ;
}
/*
* Register a new device that wants to connect
*
* A new device wants to connect , so we add it to the Connect - Ack
* list . We give it an address in the unauthorized range ( bit 8 set ) ;
* user space will have to drive authorization further on .
*
* @ dev_addr : address to use for the device ( which is also the port
* number ) .
*
* @ wusbhc - > mutex must be taken
*/
static struct wusb_dev * wusbhc_cack_add ( struct wusbhc * wusbhc ,
struct wusb_dn_connect * dnc ,
const char * pr_cdid , u8 port_idx )
{
struct device * dev = wusbhc - > dev ;
struct wusb_dev * wusb_dev ;
int new_connection = wusb_dn_connect_new_connection ( dnc ) ;
u8 dev_addr ;
int result ;
/* Is it registered already? */
list_for_each_entry ( wusb_dev , & wusbhc - > cack_list , cack_node )
if ( ! memcmp ( & wusb_dev - > cdid , & dnc - > CDID ,
sizeof ( wusb_dev - > cdid ) ) )
return wusb_dev ;
/* We don't have it, create an entry, register it */
wusb_dev = wusb_dev_alloc ( wusbhc ) ;
2008-10-16 16:56:53 +04:00
if ( wusb_dev = = NULL )
2008-09-17 19:34:24 +04:00
return NULL ;
wusb_dev_init ( wusb_dev ) ;
wusb_dev - > cdid = dnc - > CDID ;
wusb_dev - > port_idx = port_idx ;
/*
* Devices are always available within the cluster reservation
* and since the hardware will take the intersection of the
* per - device availability and the cluster reservation , the
* per - device availability can simply be set to always
* available .
*/
bitmap_fill ( wusb_dev - > availability . bm , UWB_NUM_MAS ) ;
/* FIXME: handle reconnects instead of assuming connects are
always new . */
if ( 1 & & new_connection = = 0 )
new_connection = 1 ;
if ( new_connection ) {
dev_addr = ( port_idx + 2 ) | WUSB_DEV_ADDR_UNAUTH ;
dev_info ( dev , " Connecting new WUSB device to address %u, "
" port %u \n " , dev_addr , port_idx ) ;
result = wusb_set_dev_addr ( wusbhc , wusb_dev , dev_addr ) ;
2008-09-17 19:34:31 +04:00
if ( result < 0 )
return NULL ;
2008-09-17 19:34:24 +04:00
}
wusb_dev - > entry_ts = jiffies ;
list_add_tail ( & wusb_dev - > cack_node , & wusbhc - > cack_list ) ;
wusbhc - > cack_count + + ;
wusbhc_fill_cack_ie ( wusbhc ) ;
2008-12-22 21:22:50 +03:00
2008-09-17 19:34:24 +04:00
return wusb_dev ;
}
/*
* Remove a Connect - Ack context entry from the HCs view
*
* @ wusbhc - > mutex must be taken
*/
static void wusbhc_cack_rm ( struct wusbhc * wusbhc , struct wusb_dev * wusb_dev )
{
list_del_init ( & wusb_dev - > cack_node ) ;
wusbhc - > cack_count - - ;
wusbhc_fill_cack_ie ( wusbhc ) ;
}
/*
* @ wusbhc - > mutex must be taken */
static
void wusbhc_devconnect_acked ( struct wusbhc * wusbhc , struct wusb_dev * wusb_dev )
{
wusbhc_cack_rm ( wusbhc , wusb_dev ) ;
if ( wusbhc - > cack_count )
wusbhc_mmcie_set ( wusbhc , 0 , 0 , & wusbhc - > cack_ie . hdr ) ;
else
wusbhc_mmcie_rm ( wusbhc , & wusbhc - > cack_ie . hdr ) ;
}
static void wusbhc_devconnect_acked_work ( struct work_struct * work )
{
struct wusb_dev * wusb_dev = container_of ( work , struct wusb_dev ,
devconnect_acked_work ) ;
struct wusbhc * wusbhc = wusb_dev - > wusbhc ;
mutex_lock ( & wusbhc - > mutex ) ;
wusbhc_devconnect_acked ( wusbhc , wusb_dev ) ;
mutex_unlock ( & wusbhc - > mutex ) ;
2009-04-08 21:36:32 +04:00
wusb_dev_put ( wusb_dev ) ;
2008-09-17 19:34:24 +04:00
}
/*
* Ack a device for connection
*
* FIXME : docs
*
* @ pr_cdid : Printable CDID . . . hex Use @ dnc - > cdid for the real deal .
*
* So we get the connect ack IE ( may have been allocated already ) ,
* find an empty connect block , an empty virtual port , create an
* address with it ( see below ) , make it an unauth addr [ bit 7 set ] and
* set the MMC .
*
* Addresses : because WUSB hosts have no downstream hubs , we can do a
* 1 : 1 mapping between ' port number ' and device
* address . This simplifies many things , as during this
* initial connect phase the USB stack has no knoledge of
* the device and hasn ' t assigned an address yet - - we know
* USB ' s choose_address ( ) will use the same euristics we
* use here , so we can assume which address will be assigned .
*
* USB stack always assigns address 1 to the root hub , so
* to the port number we add 2 ( thus virtual port # 0 is
* addr # 2 ) .
*
* @ wusbhc shall be referenced
*/
static
void wusbhc_devconnect_ack ( struct wusbhc * wusbhc , struct wusb_dn_connect * dnc ,
const char * pr_cdid )
{
int result ;
struct device * dev = wusbhc - > dev ;
struct wusb_dev * wusb_dev ;
struct wusb_port * port ;
unsigned idx , devnum ;
mutex_lock ( & wusbhc - > mutex ) ;
/* Check we are not handling it already */
for ( idx = 0 ; idx < wusbhc - > ports_max ; idx + + ) {
port = wusb_port_by_idx ( wusbhc , idx ) ;
if ( port - > wusb_dev
2008-10-16 16:56:53 +04:00
& & memcmp ( & dnc - > CDID , & port - > wusb_dev - > cdid , sizeof ( dnc - > CDID ) ) = = 0 )
2008-09-17 19:34:24 +04:00
goto error_unlock ;
}
/* Look up those fake ports we have for a free one */
for ( idx = 0 ; idx < wusbhc - > ports_max ; idx + + ) {
port = wusb_port_by_idx ( wusbhc , idx ) ;
if ( ( port - > status & USB_PORT_STAT_POWER )
& & ! ( port - > status & USB_PORT_STAT_CONNECTION ) )
break ;
}
if ( idx > = wusbhc - > ports_max ) {
dev_err ( dev , " Host controller can't connect more devices "
" (%u already connected); device %s rejected \n " ,
wusbhc - > ports_max , pr_cdid ) ;
/* NOTE: we could send a WUIE_Disconnect here, but we haven't
* event acked , so the device will eventually timeout the
* connection , right ? */
goto error_unlock ;
}
devnum = idx + 2 ;
/* Make sure we are using no crypto on that "virtual port" */
wusbhc - > set_ptk ( wusbhc , idx , 0 , NULL , 0 ) ;
/* Grab a filled in Connect-Ack context, fill out the
* Connect - Ack Wireless USB IE , set the MMC */
wusb_dev = wusbhc_cack_add ( wusbhc , dnc , pr_cdid , idx ) ;
if ( wusb_dev = = NULL )
goto error_unlock ;
result = wusbhc_mmcie_set ( wusbhc , 0 , 0 , & wusbhc - > cack_ie . hdr ) ;
if ( result < 0 )
goto error_unlock ;
/* Give the device at least 2ms (WUSB1.0[7.5.1p3]), let's do
* three for a good measure */
msleep ( 3 ) ;
port - > wusb_dev = wusb_dev ;
port - > status | = USB_PORT_STAT_CONNECTION ;
port - > change | = USB_PORT_STAT_C_CONNECTION ;
/* Now the port status changed to connected; khubd will
* pick the change up and try to reset the port to bring it to
* the enabled state - - so this process returns up to the stack
2008-10-27 20:12:33 +03:00
* and it calls back into wusbhc_rh_port_reset ( ) .
2008-09-17 19:34:24 +04:00
*/
error_unlock :
mutex_unlock ( & wusbhc - > mutex ) ;
return ;
}
/*
* Disconnect a Wireless USB device from its fake port
*
* Marks the port as disconnected so that khubd can pick up the change
* and drops our knowledge about the device .
*
* Assumes there is a device connected
*
* @ port_index : zero based port number
*
* NOTE : @ wusbhc - > mutex is locked
*
* WARNING : From here it is not very safe to access anything hanging off
* wusb_dev
*/
static void __wusbhc_dev_disconnect ( struct wusbhc * wusbhc ,
struct wusb_port * port )
{
struct wusb_dev * wusb_dev = port - > wusb_dev ;
port - > status & = ~ ( USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE
| USB_PORT_STAT_SUSPEND | USB_PORT_STAT_RESET
| USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED ) ;
port - > change | = USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE ;
if ( wusb_dev ) {
2009-01-06 20:58:02 +03:00
dev_dbg ( wusbhc - > dev , " disconnecting device from port %d \n " , wusb_dev - > port_idx ) ;
2008-09-17 19:34:24 +04:00
if ( ! list_empty ( & wusb_dev - > cack_node ) )
list_del_init ( & wusb_dev - > cack_node ) ;
/* For the one in cack_add() */
wusb_dev_put ( wusb_dev ) ;
}
port - > wusb_dev = NULL ;
/* After a device disconnects, change the GTK (see [WUSB]
* section 6.2 .11 .2 ) . */
2009-04-08 21:36:33 +04:00
if ( wusbhc - > active )
wusbhc_gtk_rekey ( wusbhc ) ;
2008-09-17 19:34:24 +04:00
/* The Wireless USB part has forgotten about the device already; now
* khubd ' s timer will pick up the disconnection and remove the USB
* device from the system
*/
}
/*
* Refresh the list of keep alives to emit in the MMC
*
* Some devices don ' t respond to keep alives unless they ' ve been
* authenticated , so skip unauthenticated devices .
*
* We only publish the first four devices that have a coming timeout
* condition . Then when we are done processing those , we go for the
* next ones . We ignore the ones that have timed out already ( they ' ll
* be purged ) .
*
* This might cause the first devices to timeout the last devices in
* the port array . . . FIXME : come up with a better algorithm ?
*
* Note we can ' t do much about MMC ' s ops errors ; we hope next refresh
* will kind of handle it .
*
* NOTE : @ wusbhc - > mutex is locked
*/
static void __wusbhc_keep_alive ( struct wusbhc * wusbhc )
{
struct device * dev = wusbhc - > dev ;
unsigned cnt ;
struct wusb_dev * wusb_dev ;
struct wusb_port * wusb_port ;
struct wuie_keep_alive * ie = & wusbhc - > keep_alive_ie ;
unsigned keep_alives , old_keep_alives ;
old_keep_alives = ie - > hdr . bLength - sizeof ( ie - > hdr ) ;
keep_alives = 0 ;
for ( cnt = 0 ;
keep_alives < = WUIE_ELT_MAX & & cnt < wusbhc - > ports_max ;
cnt + + ) {
unsigned tt = msecs_to_jiffies ( wusbhc - > trust_timeout ) ;
wusb_port = wusb_port_by_idx ( wusbhc , cnt ) ;
wusb_dev = wusb_port - > wusb_dev ;
if ( wusb_dev = = NULL )
continue ;
if ( wusb_dev - > usb_dev = = NULL | | ! wusb_dev - > usb_dev - > authenticated )
continue ;
if ( time_after ( jiffies , wusb_dev - > entry_ts + tt ) ) {
dev_err ( dev , " KEEPALIVE: device %u timed out \n " ,
wusb_dev - > addr ) ;
__wusbhc_dev_disconnect ( wusbhc , wusb_port ) ;
} else if ( time_after ( jiffies , wusb_dev - > entry_ts + tt / 2 ) ) {
/* Approaching timeout cut out, need to refresh */
ie - > bDeviceAddress [ keep_alives + + ] = wusb_dev - > addr ;
}
}
if ( keep_alives & 0x1 ) /* pad to even number ([WUSB] section 7.5.9) */
ie - > bDeviceAddress [ keep_alives + + ] = 0x7f ;
ie - > hdr . bLength = sizeof ( ie - > hdr ) +
keep_alives * sizeof ( ie - > bDeviceAddress [ 0 ] ) ;
2008-10-16 16:56:53 +04:00
if ( keep_alives > 0 )
wusbhc_mmcie_set ( wusbhc , 10 , 5 , & ie - > hdr ) ;
else if ( old_keep_alives ! = 0 )
2008-09-17 19:34:24 +04:00
wusbhc_mmcie_rm ( wusbhc , & ie - > hdr ) ;
}
/*
* Do a run through all devices checking for timeouts
*/
static void wusbhc_keep_alive_run ( struct work_struct * ws )
{
2009-04-03 03:56:54 +04:00
struct delayed_work * dw = to_delayed_work ( ws ) ;
2008-11-25 17:23:40 +03:00
struct wusbhc * wusbhc = container_of ( dw , struct wusbhc , keep_alive_timer ) ;
mutex_lock ( & wusbhc - > mutex ) ;
__wusbhc_keep_alive ( wusbhc ) ;
mutex_unlock ( & wusbhc - > mutex ) ;
queue_delayed_work ( wusbd , & wusbhc - > keep_alive_timer ,
msecs_to_jiffies ( wusbhc - > trust_timeout / 2 ) ) ;
2008-09-17 19:34:24 +04:00
}
/*
* Find the wusb_dev from its device address .
*
* The device can be found directly from the address ( see
* wusb_cack_add ( ) for where the device address is set to port_idx
* + 2 ) , except when the address is zero .
*/
static struct wusb_dev * wusbhc_find_dev_by_addr ( struct wusbhc * wusbhc , u8 addr )
{
int p ;
if ( addr = = 0xff ) /* unconnected */
return NULL ;
if ( addr > 0 ) {
int port = ( addr & ~ 0x80 ) - 2 ;
if ( port < 0 | | port > = wusbhc - > ports_max )
return NULL ;
return wusb_port_by_idx ( wusbhc , port ) - > wusb_dev ;
}
/* Look for the device with address 0. */
for ( p = 0 ; p < wusbhc - > ports_max ; p + + ) {
struct wusb_dev * wusb_dev = wusb_port_by_idx ( wusbhc , p ) - > wusb_dev ;
if ( wusb_dev & & wusb_dev - > addr = = addr )
return wusb_dev ;
}
return NULL ;
}
/*
* Handle a DN_Alive notification ( WUSB1 .0 [ 7.6 .1 ] )
*
* This just updates the device activity timestamp and then refreshes
* the keep alive IE .
*
* @ wusbhc shall be referenced and unlocked
*/
static void wusbhc_handle_dn_alive ( struct wusbhc * wusbhc , struct wusb_dev * wusb_dev )
{
mutex_lock ( & wusbhc - > mutex ) ;
wusb_dev - > entry_ts = jiffies ;
__wusbhc_keep_alive ( wusbhc ) ;
mutex_unlock ( & wusbhc - > mutex ) ;
}
/*
* Handle a DN_Connect notification ( WUSB1 .0 [ 7.6 .1 ] )
*
* @ wusbhc
* @ pkt_hdr
* @ size : Size of the buffer where the notification resides ; if the
* notification data suggests there should be more data than
* available , an error will be signaled and the whole buffer
* consumed .
*
* @ wusbhc - > mutex shall be held
*/
static void wusbhc_handle_dn_connect ( struct wusbhc * wusbhc ,
struct wusb_dn_hdr * dn_hdr ,
size_t size )
{
struct device * dev = wusbhc - > dev ;
struct wusb_dn_connect * dnc ;
char pr_cdid [ WUSB_CKHDID_STRSIZE ] ;
static const char * beacon_behaviour [ ] = {
" reserved " ,
" self-beacon " ,
" directed-beacon " ,
" no-beacon "
} ;
if ( size < sizeof ( * dnc ) ) {
dev_err ( dev , " DN CONNECT: short notification (%zu < %zu) \n " ,
size , sizeof ( * dnc ) ) ;
2008-12-22 21:22:50 +03:00
return ;
2008-09-17 19:34:24 +04:00
}
dnc = container_of ( dn_hdr , struct wusb_dn_connect , hdr ) ;
ckhdid_printf ( pr_cdid , sizeof ( pr_cdid ) , & dnc - > CDID ) ;
dev_info ( dev , " DN CONNECT: device %s @ %x (%s) wants to %s \n " ,
pr_cdid ,
wusb_dn_connect_prev_dev_addr ( dnc ) ,
beacon_behaviour [ wusb_dn_connect_beacon_behavior ( dnc ) ] ,
wusb_dn_connect_new_connection ( dnc ) ? " connect " : " reconnect " ) ;
/* ACK the connect */
wusbhc_devconnect_ack ( wusbhc , dnc , pr_cdid ) ;
}
/*
* Handle a DN_Disconnect notification ( WUSB1 .0 [ 7.6 .1 ] )
*
* Device is going down - - do the disconnect .
*
* @ wusbhc shall be referenced and unlocked
*/
static void wusbhc_handle_dn_disconnect ( struct wusbhc * wusbhc , struct wusb_dev * wusb_dev )
{
struct device * dev = wusbhc - > dev ;
dev_info ( dev , " DN DISCONNECT: device 0x%02x going down \n " , wusb_dev - > addr ) ;
mutex_lock ( & wusbhc - > mutex ) ;
__wusbhc_dev_disconnect ( wusbhc , wusb_port_by_idx ( wusbhc , wusb_dev - > port_idx ) ) ;
mutex_unlock ( & wusbhc - > mutex ) ;
}
/*
* Handle a Device Notification coming a host
*
* The Device Notification comes from a host ( HWA , DWA or WHCI )
* wrapped in a set of headers . Somebody else has peeled off those
* headers for us and we just get one Device Notifications .
*
* Invalid DNs ( e . g . , too short ) are discarded .
*
* @ wusbhc shall be referenced
*
* FIXMES :
* - implement priorities as in WUSB1 .0 [ Table 7 - 55 ] ?
*/
void wusbhc_handle_dn ( struct wusbhc * wusbhc , u8 srcaddr ,
struct wusb_dn_hdr * dn_hdr , size_t size )
{
struct device * dev = wusbhc - > dev ;
struct wusb_dev * wusb_dev ;
if ( size < sizeof ( struct wusb_dn_hdr ) ) {
dev_err ( dev , " DN data shorter than DN header (%d < %d) \n " ,
( int ) size , ( int ) sizeof ( struct wusb_dn_hdr ) ) ;
2008-12-22 21:22:50 +03:00
return ;
2008-09-17 19:34:24 +04:00
}
wusb_dev = wusbhc_find_dev_by_addr ( wusbhc , srcaddr ) ;
if ( wusb_dev = = NULL & & dn_hdr - > bType ! = WUSB_DN_CONNECT ) {
dev_dbg ( dev , " ignoring DN %d from unconnected device %02x \n " ,
dn_hdr - > bType , srcaddr ) ;
2008-12-22 21:22:50 +03:00
return ;
2008-09-17 19:34:24 +04:00
}
switch ( dn_hdr - > bType ) {
case WUSB_DN_CONNECT :
wusbhc_handle_dn_connect ( wusbhc , dn_hdr , size ) ;
break ;
case WUSB_DN_ALIVE :
wusbhc_handle_dn_alive ( wusbhc , wusb_dev ) ;
break ;
case WUSB_DN_DISCONNECT :
wusbhc_handle_dn_disconnect ( wusbhc , wusb_dev ) ;
break ;
case WUSB_DN_MASAVAILCHANGED :
case WUSB_DN_RWAKE :
case WUSB_DN_SLEEP :
/* FIXME: handle these DNs. */
break ;
case WUSB_DN_EPRDY :
/* The hardware handles these. */
break ;
default :
dev_warn ( dev , " unknown DN %u (%d octets) from %u \n " ,
dn_hdr - > bType , ( int ) size , srcaddr ) ;
}
}
EXPORT_SYMBOL_GPL ( wusbhc_handle_dn ) ;
/*
* Disconnect a WUSB device from a the cluster
*
* @ wusbhc
* @ port Fake port where the device is ( wusbhc index , not USB port number ) .
*
* In Wireless USB , a disconnect is basically telling the device he is
* being disconnected and forgetting about him .
*
* We send the device a Device Disconnect IE ( WUSB1 .0 [ 7.5 .11 ] ) for 100
* ms and then keep going .
*
* We don ' t do much in case of error ; we always pretend we disabled
* the port and disconnected the device . If physically the request
* didn ' t get there ( many things can fail in the way there ) , the stack
* will reject the device ' s communication attempts .
*
* @ wusbhc should be refcounted and locked
*/
void __wusbhc_dev_disable ( struct wusbhc * wusbhc , u8 port_idx )
{
int result ;
struct device * dev = wusbhc - > dev ;
struct wusb_dev * wusb_dev ;
struct wuie_disconnect * ie ;
wusb_dev = wusb_port_by_idx ( wusbhc , port_idx ) - > wusb_dev ;
if ( wusb_dev = = NULL ) {
/* reset no device? ignore */
dev_dbg ( dev , " DISCONNECT: no device at port %u, ignoring \n " ,
port_idx ) ;
2008-12-22 21:22:50 +03:00
return ;
2008-09-17 19:34:24 +04:00
}
__wusbhc_dev_disconnect ( wusbhc , wusb_port_by_idx ( wusbhc , port_idx ) ) ;
ie = kzalloc ( sizeof ( * ie ) , GFP_KERNEL ) ;
if ( ie = = NULL )
2008-12-22 21:22:50 +03:00
return ;
2008-09-17 19:34:24 +04:00
ie - > hdr . bLength = sizeof ( * ie ) ;
ie - > hdr . bIEIdentifier = WUIE_ID_DEVICE_DISCONNECT ;
ie - > bDeviceAddress = wusb_dev - > addr ;
result = wusbhc_mmcie_set ( wusbhc , 0 , 0 , & ie - > hdr ) ;
2008-12-22 21:22:50 +03:00
if ( result < 0 )
2008-09-17 19:34:24 +04:00
dev_err ( dev , " DISCONNECT: can't set MMC: %d \n " , result ) ;
2008-12-22 21:22:50 +03:00
else {
/* At least 6 MMCs, assuming at least 1 MMC per zone. */
msleep ( 7 * 4 ) ;
wusbhc_mmcie_rm ( wusbhc , & ie - > hdr ) ;
2008-09-17 19:34:24 +04:00
}
kfree ( ie ) ;
}
/*
* Walk over the BOS descriptor , verify and grok it
*
* @ usb_dev : referenced
* @ wusb_dev : referenced and unlocked
*
* The BOS descriptor is defined at WUSB1 .0 [ 7.4 .1 ] , and it defines a
* " flexible " way to wrap all kinds of descriptors inside an standard
* descriptor ( wonder why they didn ' t use normal descriptors ,
* btw ) . Not like they lack code .
*
* At the end we go to look for the WUSB Device Capabilities
* ( WUSB1 .0 [ 7.4 .1 .1 ] ) that is wrapped in a device capability descriptor
* that is part of the BOS descriptor set . That tells us what does the
* device support ( dual role , beacon type , UWB PHY rates ) .
*/
static int wusb_dev_bos_grok ( struct usb_device * usb_dev ,
struct wusb_dev * wusb_dev ,
struct usb_bos_descriptor * bos , size_t desc_size )
{
ssize_t result ;
struct device * dev = & usb_dev - > dev ;
void * itr , * top ;
/* Walk over BOS capabilities, verify them */
itr = ( void * ) bos + sizeof ( * bos ) ;
top = itr + desc_size - sizeof ( * bos ) ;
while ( itr < top ) {
struct usb_dev_cap_header * cap_hdr = itr ;
size_t cap_size ;
u8 cap_type ;
if ( top - itr < sizeof ( * cap_hdr ) ) {
dev_err ( dev , " Device BUG? premature end of BOS header "
" data [offset 0x%02x]: only %zu bytes left \n " ,
( int ) ( itr - ( void * ) bos ) , top - itr ) ;
result = - ENOSPC ;
goto error_bad_cap ;
}
cap_size = cap_hdr - > bLength ;
cap_type = cap_hdr - > bDevCapabilityType ;
if ( cap_size = = 0 )
break ;
if ( cap_size > top - itr ) {
dev_err ( dev , " Device BUG? premature end of BOS data "
" [offset 0x%02x cap %02x %zu bytes]: "
" only %zu bytes left \n " ,
( int ) ( itr - ( void * ) bos ) ,
cap_type , cap_size , top - itr ) ;
result = - EBADF ;
goto error_bad_cap ;
}
switch ( cap_type ) {
case USB_CAP_TYPE_WIRELESS_USB :
if ( cap_size ! = sizeof ( * wusb_dev - > wusb_cap_descr ) )
dev_err ( dev , " Device BUG? WUSB Capability "
" descriptor is %zu bytes vs %zu "
" needed \n " , cap_size ,
sizeof ( * wusb_dev - > wusb_cap_descr ) ) ;
2008-12-22 21:22:50 +03:00
else
2008-09-17 19:34:24 +04:00
wusb_dev - > wusb_cap_descr = itr ;
break ;
default :
dev_err ( dev , " BUG? Unknown BOS capability 0x%02x "
" (%zu bytes) at offset 0x%02x \n " , cap_type ,
cap_size , ( int ) ( itr - ( void * ) bos ) ) ;
}
itr + = cap_size ;
}
result = 0 ;
error_bad_cap :
return result ;
}
/*
* Add information from the BOS descriptors to the device
*
* @ usb_dev : referenced
* @ wusb_dev : referenced and unlocked
*
* So what we do is we alloc a space for the BOS descriptor of 64
* bytes ; read the first four bytes which include the wTotalLength
* field ( WUSB1 .0 [ T7 - 26 ] ) and if it fits in those 64 bytes , read the
* whole thing . If not we realloc to that size .
*
* Then we call the groking function , that will fill up
* wusb_dev - > wusb_cap_descr , which is what we ' ll need later on .
*/
static int wusb_dev_bos_add ( struct usb_device * usb_dev ,
struct wusb_dev * wusb_dev )
{
ssize_t result ;
struct device * dev = & usb_dev - > dev ;
struct usb_bos_descriptor * bos ;
size_t alloc_size = 32 , desc_size = 4 ;
bos = kmalloc ( alloc_size , GFP_KERNEL ) ;
if ( bos = = NULL )
return - ENOMEM ;
result = usb_get_descriptor ( usb_dev , USB_DT_BOS , 0 , bos , desc_size ) ;
if ( result < 4 ) {
dev_err ( dev , " Can't get BOS descriptor or too short: %zd \n " ,
result ) ;
goto error_get_descriptor ;
}
desc_size = le16_to_cpu ( bos - > wTotalLength ) ;
if ( desc_size > = alloc_size ) {
kfree ( bos ) ;
alloc_size = desc_size ;
bos = kmalloc ( alloc_size , GFP_KERNEL ) ;
if ( bos = = NULL )
return - ENOMEM ;
}
result = usb_get_descriptor ( usb_dev , USB_DT_BOS , 0 , bos , desc_size ) ;
if ( result < 0 | | result ! = desc_size ) {
dev_err ( dev , " Can't get BOS descriptor or too short (need "
" %zu bytes): %zd \n " , desc_size , result ) ;
goto error_get_descriptor ;
}
if ( result < sizeof ( * bos )
| | le16_to_cpu ( bos - > wTotalLength ) ! = desc_size ) {
dev_err ( dev , " Can't get BOS descriptor or too short (need "
" %zu bytes): %zd \n " , desc_size , result ) ;
goto error_get_descriptor ;
}
2008-12-22 21:22:50 +03:00
2008-09-17 19:34:24 +04:00
result = wusb_dev_bos_grok ( usb_dev , wusb_dev , bos , result ) ;
if ( result < 0 )
goto error_bad_bos ;
wusb_dev - > bos = bos ;
return 0 ;
error_bad_bos :
error_get_descriptor :
kfree ( bos ) ;
wusb_dev - > wusb_cap_descr = NULL ;
return result ;
}
static void wusb_dev_bos_rm ( struct wusb_dev * wusb_dev )
{
kfree ( wusb_dev - > bos ) ;
wusb_dev - > wusb_cap_descr = NULL ;
} ;
static struct usb_wireless_cap_descriptor wusb_cap_descr_default = {
. bLength = sizeof ( wusb_cap_descr_default ) ,
. bDescriptorType = USB_DT_DEVICE_CAPABILITY ,
. bDevCapabilityType = USB_CAP_TYPE_WIRELESS_USB ,
. bmAttributes = USB_WIRELESS_BEACON_NONE ,
. wPHYRates = cpu_to_le16 ( USB_WIRELESS_PHY_53 ) ,
. bmTFITXPowerInfo = 0 ,
. bmFFITXPowerInfo = 0 ,
. bmBandGroup = cpu_to_le16 ( 0x0001 ) , /* WUSB1.0[7.4.1] bottom */
. bReserved = 0
} ;
/*
* USB stack ' s device addition Notifier Callback
*
* Called from drivers / usb / core / hub . c when a new device is added ; we
* use this hook to perform certain WUSB specific setup work on the
* new device . As well , it is the first time we can connect the
* wusb_dev and the usb_dev . So we note it down in wusb_dev and take a
* reference that we ' ll drop .
*
* First we need to determine if the device is a WUSB device ( else we
* ignore it ) . For that we use the speed setting ( USB_SPEED_VARIABLE )
* [ FIXME : maybe we ' d need something more definitive ] . If so , we track
* it ' s usb_busd and from there , the WUSB HC .
*
* Because all WUSB HCs are contained in a ' struct wusbhc ' , voila , we
* get the wusbhc for the device .
*
* We have a reference on @ usb_dev ( as we are called at the end of its
* enumeration ) .
*
* NOTE : @ usb_dev locked
*/
static void wusb_dev_add_ncb ( struct usb_device * usb_dev )
{
int result = 0 ;
struct wusb_dev * wusb_dev ;
struct wusbhc * wusbhc ;
struct device * dev = & usb_dev - > dev ;
u8 port_idx ;
if ( usb_dev - > wusb = = 0 | | usb_dev - > devnum = = 1 )
return ; /* skip non wusb and wusb RHs */
2009-02-18 17:43:47 +03:00
usb_set_device_state ( usb_dev , USB_STATE_UNAUTHENTICATED ) ;
2008-09-17 19:34:24 +04:00
wusbhc = wusbhc_get_by_usb_dev ( usb_dev ) ;
if ( wusbhc = = NULL )
goto error_nodev ;
mutex_lock ( & wusbhc - > mutex ) ;
wusb_dev = __wusb_dev_get_by_usb_dev ( wusbhc , usb_dev ) ;
port_idx = wusb_port_no_to_idx ( usb_dev - > portnum ) ;
mutex_unlock ( & wusbhc - > mutex ) ;
if ( wusb_dev = = NULL )
goto error_nodev ;
wusb_dev - > usb_dev = usb_get_dev ( usb_dev ) ;
usb_dev - > wusb_dev = wusb_dev_get ( wusb_dev ) ;
result = wusb_dev_sec_add ( wusbhc , usb_dev , wusb_dev ) ;
if ( result < 0 ) {
dev_err ( dev , " Cannot enable security: %d \n " , result ) ;
goto error_sec_add ;
}
/* Now query the device for it's BOS and attach it to wusb_dev */
result = wusb_dev_bos_add ( usb_dev , wusb_dev ) ;
if ( result < 0 ) {
dev_err ( dev , " Cannot get BOS descriptors: %d \n " , result ) ;
goto error_bos_add ;
}
result = wusb_dev_sysfs_add ( wusbhc , usb_dev , wusb_dev ) ;
if ( result < 0 )
goto error_add_sysfs ;
out :
wusb_dev_put ( wusb_dev ) ;
wusbhc_put ( wusbhc ) ;
error_nodev :
return ;
wusb_dev_sysfs_rm ( wusb_dev ) ;
error_add_sysfs :
wusb_dev_bos_rm ( wusb_dev ) ;
error_bos_add :
wusb_dev_sec_rm ( wusb_dev ) ;
error_sec_add :
mutex_lock ( & wusbhc - > mutex ) ;
__wusbhc_dev_disconnect ( wusbhc , wusb_port_by_idx ( wusbhc , port_idx ) ) ;
mutex_unlock ( & wusbhc - > mutex ) ;
goto out ;
}
/*
* Undo all the steps done at connection by the notifier callback
*
* NOTE : @ usb_dev locked
*/
static void wusb_dev_rm_ncb ( struct usb_device * usb_dev )
{
struct wusb_dev * wusb_dev = usb_dev - > wusb_dev ;
if ( usb_dev - > wusb = = 0 | | usb_dev - > devnum = = 1 )
return ; /* skip non wusb and wusb RHs */
wusb_dev_sysfs_rm ( wusb_dev ) ;
wusb_dev_bos_rm ( wusb_dev ) ;
wusb_dev_sec_rm ( wusb_dev ) ;
wusb_dev - > usb_dev = NULL ;
usb_dev - > wusb_dev = NULL ;
wusb_dev_put ( wusb_dev ) ;
usb_put_dev ( usb_dev ) ;
}
/*
* Handle notifications from the USB stack ( notifier call back )
*
* This is called when the USB stack does a
* usb_ { bus , device } _ { add , remove } ( ) so we can do WUSB specific
* handling . It is called with [ for the case of
* USB_DEVICE_ { ADD , REMOVE } with the usb_dev locked .
*/
int wusb_usb_ncb ( struct notifier_block * nb , unsigned long val ,
void * priv )
{
int result = NOTIFY_OK ;
switch ( val ) {
case USB_DEVICE_ADD :
wusb_dev_add_ncb ( priv ) ;
break ;
case USB_DEVICE_REMOVE :
wusb_dev_rm_ncb ( priv ) ;
break ;
case USB_BUS_ADD :
/* ignore (for now) */
case USB_BUS_REMOVE :
break ;
default :
WARN_ON ( 1 ) ;
result = NOTIFY_BAD ;
} ;
return result ;
}
/*
* Return a referenced wusb_dev given a @ wusbhc and @ usb_dev
*/
struct wusb_dev * __wusb_dev_get_by_usb_dev ( struct wusbhc * wusbhc ,
struct usb_device * usb_dev )
{
struct wusb_dev * wusb_dev ;
u8 port_idx ;
port_idx = wusb_port_no_to_idx ( usb_dev - > portnum ) ;
BUG_ON ( port_idx > wusbhc - > ports_max ) ;
wusb_dev = wusb_port_by_idx ( wusbhc , port_idx ) - > wusb_dev ;
if ( wusb_dev ! = NULL ) /* ops, device is gone */
wusb_dev_get ( wusb_dev ) ;
return wusb_dev ;
}
EXPORT_SYMBOL_GPL ( __wusb_dev_get_by_usb_dev ) ;
void wusb_dev_destroy ( struct kref * _wusb_dev )
{
2008-12-22 21:22:50 +03:00
struct wusb_dev * wusb_dev = container_of ( _wusb_dev , struct wusb_dev , refcnt ) ;
2008-09-17 19:34:24 +04:00
list_del_init ( & wusb_dev - > cack_node ) ;
wusb_dev_free ( wusb_dev ) ;
}
EXPORT_SYMBOL_GPL ( wusb_dev_destroy ) ;
/*
* Create all the device connect handling infrastructure
*
* This is basically the device info array , Connect Acknowledgement
* ( cack ) lists , keep - alive timers ( and delayed work thread ) .
*/
int wusbhc_devconnect_create ( struct wusbhc * wusbhc )
{
wusbhc - > keep_alive_ie . hdr . bIEIdentifier = WUIE_ID_KEEP_ALIVE ;
wusbhc - > keep_alive_ie . hdr . bLength = sizeof ( wusbhc - > keep_alive_ie . hdr ) ;
INIT_DELAYED_WORK ( & wusbhc - > keep_alive_timer , wusbhc_keep_alive_run ) ;
wusbhc - > cack_ie . hdr . bIEIdentifier = WUIE_ID_CONNECTACK ;
wusbhc - > cack_ie . hdr . bLength = sizeof ( wusbhc - > cack_ie . hdr ) ;
INIT_LIST_HEAD ( & wusbhc - > cack_list ) ;
return 0 ;
}
/*
* Release all resources taken by the devconnect stuff
*/
void wusbhc_devconnect_destroy ( struct wusbhc * wusbhc )
{
2008-12-22 21:22:50 +03:00
/* no op */
2008-09-17 19:34:24 +04:00
}
/*
* wusbhc_devconnect_start - start accepting device connections
* @ wusbhc : the WUSB HC
*
* Sets the Host Info IE to accept all new connections .
*
* FIXME : This also enables the keep alives but this is not necessary
* until there are connected and authenticated devices .
*/
2008-11-17 18:53:42 +03:00
int wusbhc_devconnect_start ( struct wusbhc * wusbhc )
2008-09-17 19:34:24 +04:00
{
struct device * dev = wusbhc - > dev ;
struct wuie_host_info * hi ;
int result ;
hi = kzalloc ( sizeof ( * hi ) , GFP_KERNEL ) ;
if ( hi = = NULL )
return - ENOMEM ;
hi - > hdr . bLength = sizeof ( * hi ) ;
hi - > hdr . bIEIdentifier = WUIE_ID_HOST_INFO ;
hi - > attributes = cpu_to_le16 ( ( wusbhc - > rsv - > stream < < 3 ) | WUIE_HI_CAP_ALL ) ;
2008-11-17 18:53:42 +03:00
hi - > CHID = wusbhc - > chid ;
2008-09-17 19:34:24 +04:00
result = wusbhc_mmcie_set ( wusbhc , 0 , 0 , & hi - > hdr ) ;
if ( result < 0 ) {
dev_err ( dev , " Cannot add Host Info MMCIE: %d \n " , result ) ;
goto error_mmcie_set ;
}
wusbhc - > wuie_host_info = hi ;
queue_delayed_work ( wusbd , & wusbhc - > keep_alive_timer ,
( wusbhc - > trust_timeout * CONFIG_HZ ) / 1000 / 2 ) ;
return 0 ;
error_mmcie_set :
kfree ( hi ) ;
return result ;
}
/*
* wusbhc_devconnect_stop - stop managing connected devices
* @ wusbhc : the WUSB HC
*
2009-04-08 21:36:33 +04:00
* Disconnects any devices still connected , stops the keep alives and
* removes the Host Info IE .
2008-09-17 19:34:24 +04:00
*/
void wusbhc_devconnect_stop ( struct wusbhc * wusbhc )
{
2009-04-08 21:36:33 +04:00
int i ;
2008-09-17 19:34:24 +04:00
2009-04-08 21:36:33 +04:00
mutex_lock ( & wusbhc - > mutex ) ;
for ( i = 0 ; i < wusbhc - > ports_max ; i + + ) {
if ( wusbhc - > port [ i ] . wusb_dev )
__wusbhc_dev_disconnect ( wusbhc , & wusbhc - > port [ i ] ) ;
}
mutex_unlock ( & wusbhc - > mutex ) ;
cancel_delayed_work_sync ( & wusbhc - > keep_alive_timer ) ;
2008-09-17 19:34:24 +04:00
wusbhc_mmcie_rm ( wusbhc , & wusbhc - > wuie_host_info - > hdr ) ;
kfree ( wusbhc - > wuie_host_info ) ;
wusbhc - > wuie_host_info = NULL ;
}
/*
* wusb_set_dev_addr - set the WUSB device address used by the host
* @ wusbhc : the WUSB HC the device is connect to
* @ wusb_dev : the WUSB device
* @ addr : new device address
*/
int wusb_set_dev_addr ( struct wusbhc * wusbhc , struct wusb_dev * wusb_dev , u8 addr )
{
int result ;
wusb_dev - > addr = addr ;
result = wusbhc - > dev_info_set ( wusbhc , wusb_dev ) ;
2008-09-17 19:34:31 +04:00
if ( result < 0 )
2008-09-17 19:34:24 +04:00
dev_err ( wusbhc - > dev , " device %d: failed to set device "
" address \n " , wusb_dev - > port_idx ) ;
else
dev_info ( wusbhc - > dev , " device %d: %s addr %u \n " ,
wusb_dev - > port_idx ,
( addr & WUSB_DEV_ADDR_UNAUTH ) ? " unauth " : " auth " ,
wusb_dev - > addr ) ;
return result ;
}