2008-09-17 16:34:23 +01:00
/*
* Wireless USB Host Controller
* Root Hub operations
*
*
* Copyright ( C ) 2005 - 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 .
*
*
* We fake a root hub that has fake ports ( as many as simultaneous
* devices the Wireless USB Host Controller can deal with ) . For each
* port we keep an state in @ wusbhc - > port [ index ] identical to the one
* specified in the USB2 .0 [ ch11 ] spec and some extra device
* information that complements the one in ' struct usb_device ' ( as
* this lacs a hcpriv pointer ) .
*
* Note this is common to WHCI and HWA host controllers .
*
* Through here we enable most of the state changes that the USB stack
* will use to connect or disconnect devices . We need to do some
* forced adaptation of Wireless USB device states vs . wired :
*
* USB : WUSB :
*
* Port Powered - off port slot n / a
* Powered - on port slot available
* Disconnected port slot available
* Connected port slot assigned device
* device sent DN_Connect
* device was authenticated
* Enabled device is authenticated , transitioned
* from unauth - > auth - > default address
* - > enabled
* Reset disconnect
* Disable disconnect
*
* This maps the standard USB port states with the WUSB device states
* so we can fake ports without having to modify the USB stack .
*
* FIXME : this process will change in the future
*
*
* ENTRY POINTS
*
* Our entry points into here are , as in hcd . c , the USB stack root hub
* ops defined in the usb_hcd struct :
*
* wusbhc_rh_status_data ( ) Provide hub and port status data bitmap
*
* wusbhc_rh_control ( ) Execution of all the major requests
* you can do to a hub ( Set | Clear
* features , get descriptors , status , etc ) .
*
* wusbhc_rh_ [ suspend | resume ] ( ) That
*
* wusbhc_rh_start_port_reset ( ) ? ? ? unimplemented
*/
# include "wusbhc.h"
/*
* Reset a fake port
*
2008-10-27 17:12:33 +00:00
* Using a Reset Device IE is too heavyweight as it causes the device
* to enter the UnConnected state and leave the cluster , this can mean
* that when the device reconnects it is connected to a different fake
* port .
2008-09-17 16:34:23 +01:00
*
2008-10-27 17:12:33 +00:00
* Instead , reset authenticated devices with a SetAddress ( 0 ) , followed
* by a SetAddresss ( AuthAddr ) .
*
* For unauthenticated devices just pretend to reset but do nothing .
* If the device initialization continues to fail it will eventually
* time out after TrustTimeout and enter the UnConnected state .
2008-09-17 16:34:23 +01:00
*
* @ wusbhc is assumed referenced and @ wusbhc - > mutex unlocked .
*
* Supposedly we are the only thread accesing @ wusbhc - > port ; in any
* case , maybe we should move the mutex locking from
* wusbhc_devconnect_auth ( ) to here .
*
* @ port_idx refers to the wusbhc ' s port index , not the USB port number
*/
static int wusbhc_rh_port_reset ( struct wusbhc * wusbhc , u8 port_idx )
{
int result = 0 ;
struct wusb_port * port = wusb_port_by_idx ( wusbhc , port_idx ) ;
2008-10-27 17:12:33 +00:00
struct wusb_dev * wusb_dev = port - > wusb_dev ;
port - > status | = USB_PORT_STAT_RESET ;
port - > change | = USB_PORT_STAT_C_RESET ;
2008-09-17 16:34:23 +01:00
2008-10-27 17:12:33 +00:00
if ( wusb_dev - > addr & WUSB_DEV_ADDR_UNAUTH )
result = 0 ;
2008-09-17 16:34:23 +01:00
else
2008-10-27 17:12:33 +00:00
result = wusb_dev_update_address ( wusbhc , wusb_dev ) ;
port - > status & = ~ USB_PORT_STAT_RESET ;
port - > status | = USB_PORT_STAT_ENABLE ;
port - > change | = USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_ENABLE ;
2008-09-17 16:34:23 +01:00
return result ;
}
/*
* Return the hub change status bitmap
*
* The bits in the change status bitmap are cleared when a
* ClearPortFeature request is issued ( USB2 .0 [ 11.12 .3 , 11.12 .4 ] .
*
* @ wusbhc is assumed referenced and @ wusbhc - > mutex unlocked .
*
* WARNING ! ! This gets called from atomic context ; we cannot get the
* mutex - - the only race condition we can find is some bit
* changing just after we copy it , which shouldn ' t be too
* big of a problem [ and we can ' t make it an spinlock
* because other parts need to take it and sleep ] .
*
* @ usb_hcd is refcounted , so it won ' t dissapear under us
* and before killing a host , the polling of the root hub
* would be stopped anyway .
*/
int wusbhc_rh_status_data ( struct usb_hcd * usb_hcd , char * _buf )
{
struct wusbhc * wusbhc = usb_hcd_to_wusbhc ( usb_hcd ) ;
size_t cnt , size ;
unsigned long * buf = ( unsigned long * ) _buf ;
/* WE DON'T LOCK, see comment */
size = wusbhc - > ports_max + 1 /* hub bit */ ;
size = ( size + 8 - 1 ) / 8 ; /* round to bytes */
for ( cnt = 0 ; cnt < wusbhc - > ports_max ; cnt + + )
if ( wusb_port_by_idx ( wusbhc , cnt ) - > change )
set_bit ( cnt + 1 , buf ) ;
else
clear_bit ( cnt + 1 , buf ) ;
return size ;
}
EXPORT_SYMBOL_GPL ( wusbhc_rh_status_data ) ;
/*
* Return the hub ' s desciptor
*
* NOTE : almost cut and paste from ehci - hub . c
*
* @ wusbhc is assumed referenced and @ wusbhc - > mutex unlocked
*/
static int wusbhc_rh_get_hub_descr ( struct wusbhc * wusbhc , u16 wValue ,
u16 wIndex ,
struct usb_hub_descriptor * descr ,
u16 wLength )
{
u16 temp = 1 + ( wusbhc - > ports_max / 8 ) ;
u8 length = 7 + 2 * temp ;
if ( wLength < length )
return - ENOSPC ;
descr - > bDescLength = 7 + 2 * temp ;
descr - > bDescriptorType = 0x29 ; /* HUB type */
descr - > bNbrPorts = wusbhc - > ports_max ;
descr - > wHubCharacteristics = cpu_to_le16 (
0x00 /* All ports power at once */
| 0x00 /* not part of compound device */
| 0x10 /* No overcurrent protection */
| 0x00 /* 8 FS think time FIXME ?? */
| 0x00 ) ; /* No port indicators */
descr - > bPwrOn2PwrGood = 0 ;
descr - > bHubContrCurrent = 0 ;
/* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
memset ( & descr - > bitmap [ 0 ] , 0 , temp ) ;
memset ( & descr - > bitmap [ temp ] , 0xff , temp ) ;
return 0 ;
}
/*
* Clear a hub feature
*
* @ wusbhc is assumed referenced and @ wusbhc - > mutex unlocked .
*
* Nothing to do , so no locking needed ; )
*/
static int wusbhc_rh_clear_hub_feat ( struct wusbhc * wusbhc , u16 feature )
{
int result ;
switch ( feature ) {
case C_HUB_LOCAL_POWER :
/* FIXME: maybe plug bit 0 to the power input status,
* if any ?
* see wusbhc_rh_get_hub_status ( ) */
case C_HUB_OVER_CURRENT :
result = 0 ;
break ;
default :
result = - EPIPE ;
}
return result ;
}
/*
* Return hub status ( it is always zero . . . )
*
* @ wusbhc is assumed referenced and @ wusbhc - > mutex unlocked .
*
* Nothing to do , so no locking needed ; )
*/
static int wusbhc_rh_get_hub_status ( struct wusbhc * wusbhc , u32 * buf ,
u16 wLength )
{
/* FIXME: maybe plug bit 0 to the power input status (if any)? */
* buf = 0 ;
return 0 ;
}
/*
* Set a port feature
*
* @ wusbhc is assumed referenced and @ wusbhc - > mutex unlocked .
*/
static int wusbhc_rh_set_port_feat ( struct wusbhc * wusbhc , u16 feature ,
u8 selector , u8 port_idx )
{
struct device * dev = wusbhc - > dev ;
if ( port_idx > wusbhc - > ports_max )
2008-12-22 18:22:50 +00:00
return - EINVAL ;
2008-09-17 16:34:23 +01:00
switch ( feature ) {
/* According to USB2.0[11.24.2.13]p2, these features
* are not required to be implemented . */
case USB_PORT_FEAT_C_OVER_CURRENT :
case USB_PORT_FEAT_C_ENABLE :
case USB_PORT_FEAT_C_SUSPEND :
case USB_PORT_FEAT_C_CONNECTION :
case USB_PORT_FEAT_C_RESET :
2008-12-22 18:22:50 +00:00
return 0 ;
2008-09-17 16:34:23 +01:00
case USB_PORT_FEAT_POWER :
/* No such thing, but we fake it works */
mutex_lock ( & wusbhc - > mutex ) ;
wusb_port_by_idx ( wusbhc , port_idx ) - > status | = USB_PORT_STAT_POWER ;
mutex_unlock ( & wusbhc - > mutex ) ;
2008-12-22 18:22:50 +00:00
return 0 ;
2008-09-17 16:34:23 +01:00
case USB_PORT_FEAT_RESET :
2008-12-22 18:22:50 +00:00
return wusbhc_rh_port_reset ( wusbhc , port_idx ) ;
2008-09-17 16:34:23 +01:00
case USB_PORT_FEAT_ENABLE :
case USB_PORT_FEAT_SUSPEND :
dev_err ( dev , " (port_idx %d) set feat %d/%d UNIMPLEMENTED \n " ,
port_idx , feature , selector ) ;
2008-12-22 18:22:50 +00:00
return - ENOSYS ;
2008-09-17 16:34:23 +01:00
default :
dev_err ( dev , " (port_idx %d) set feat %d/%d UNKNOWN \n " ,
port_idx , feature , selector ) ;
2008-12-22 18:22:50 +00:00
return - EPIPE ;
2008-09-17 16:34:23 +01:00
}
2008-12-22 18:22:50 +00:00
return 0 ;
2008-09-17 16:34:23 +01:00
}
/*
* Clear a port feature . . .
*
* @ wusbhc is assumed referenced and @ wusbhc - > mutex unlocked .
*/
static int wusbhc_rh_clear_port_feat ( struct wusbhc * wusbhc , u16 feature ,
u8 selector , u8 port_idx )
{
2008-12-22 18:22:50 +00:00
int result = 0 ;
2008-09-17 16:34:23 +01:00
struct device * dev = wusbhc - > dev ;
if ( port_idx > wusbhc - > ports_max )
2008-12-22 18:22:50 +00:00
return - EINVAL ;
2008-09-17 16:34:23 +01:00
mutex_lock ( & wusbhc - > mutex ) ;
switch ( feature ) {
case USB_PORT_FEAT_POWER : /* fake port always on */
/* According to USB2.0[11.24.2.7.1.4], no need to implement? */
case USB_PORT_FEAT_C_OVER_CURRENT :
break ;
case USB_PORT_FEAT_C_RESET :
wusb_port_by_idx ( wusbhc , port_idx ) - > change & = ~ USB_PORT_STAT_C_RESET ;
break ;
case USB_PORT_FEAT_C_CONNECTION :
wusb_port_by_idx ( wusbhc , port_idx ) - > change & = ~ USB_PORT_STAT_C_CONNECTION ;
break ;
case USB_PORT_FEAT_ENABLE :
__wusbhc_dev_disable ( wusbhc , port_idx ) ;
break ;
case USB_PORT_FEAT_C_ENABLE :
wusb_port_by_idx ( wusbhc , port_idx ) - > change & = ~ USB_PORT_STAT_C_ENABLE ;
break ;
case USB_PORT_FEAT_SUSPEND :
case USB_PORT_FEAT_C_SUSPEND :
dev_err ( dev , " (port_idx %d) Clear feat %d/%d UNIMPLEMENTED \n " ,
port_idx , feature , selector ) ;
result = - ENOSYS ;
break ;
default :
dev_err ( dev , " (port_idx %d) Clear feat %d/%d UNKNOWN \n " ,
port_idx , feature , selector ) ;
result = - EPIPE ;
break ;
}
mutex_unlock ( & wusbhc - > mutex ) ;
2008-12-22 18:22:50 +00:00
2008-09-17 16:34:23 +01:00
return result ;
}
/*
* Return the port ' s status
*
* @ wusbhc is assumed referenced and @ wusbhc - > mutex unlocked .
*/
static int wusbhc_rh_get_port_status ( struct wusbhc * wusbhc , u16 port_idx ,
u32 * _buf , u16 wLength )
{
u16 * buf = ( u16 * ) _buf ;
if ( port_idx > wusbhc - > ports_max )
2008-12-22 18:22:50 +00:00
return - EINVAL ;
2008-09-17 16:34:23 +01:00
mutex_lock ( & wusbhc - > mutex ) ;
buf [ 0 ] = cpu_to_le16 ( wusb_port_by_idx ( wusbhc , port_idx ) - > status ) ;
buf [ 1 ] = cpu_to_le16 ( wusb_port_by_idx ( wusbhc , port_idx ) - > change ) ;
mutex_unlock ( & wusbhc - > mutex ) ;
2008-12-22 18:22:50 +00:00
return 0 ;
2008-09-17 16:34:23 +01:00
}
/*
* Entry point for Root Hub operations
*
* @ wusbhc is assumed referenced and @ wusbhc - > mutex unlocked .
*/
int wusbhc_rh_control ( struct usb_hcd * usb_hcd , u16 reqntype , u16 wValue ,
u16 wIndex , char * buf , u16 wLength )
{
int result = - ENOSYS ;
struct wusbhc * wusbhc = usb_hcd_to_wusbhc ( usb_hcd ) ;
switch ( reqntype ) {
case GetHubDescriptor :
result = wusbhc_rh_get_hub_descr (
wusbhc , wValue , wIndex ,
( struct usb_hub_descriptor * ) buf , wLength ) ;
break ;
case ClearHubFeature :
result = wusbhc_rh_clear_hub_feat ( wusbhc , wValue ) ;
break ;
case GetHubStatus :
result = wusbhc_rh_get_hub_status ( wusbhc , ( u32 * ) buf , wLength ) ;
break ;
case SetPortFeature :
result = wusbhc_rh_set_port_feat ( wusbhc , wValue , wIndex > > 8 ,
( wIndex & 0xff ) - 1 ) ;
break ;
case ClearPortFeature :
result = wusbhc_rh_clear_port_feat ( wusbhc , wValue , wIndex > > 8 ,
( wIndex & 0xff ) - 1 ) ;
break ;
case GetPortStatus :
result = wusbhc_rh_get_port_status ( wusbhc , wIndex - 1 ,
( u32 * ) buf , wLength ) ;
break ;
case SetHubFeature :
default :
dev_err ( wusbhc - > dev , " %s (%p [%p], %x, %x, %x, %p, %x) "
" UNIMPLEMENTED \n " , __func__ , usb_hcd , wusbhc , reqntype ,
wValue , wIndex , buf , wLength ) ;
/* dump_stack(); */
result = - ENOSYS ;
}
return result ;
}
EXPORT_SYMBOL_GPL ( wusbhc_rh_control ) ;
int wusbhc_rh_suspend ( struct usb_hcd * usb_hcd )
{
struct wusbhc * wusbhc = usb_hcd_to_wusbhc ( usb_hcd ) ;
dev_err ( wusbhc - > dev , " %s (%p [%p]) UNIMPLEMENTED \n " , __func__ ,
usb_hcd , wusbhc ) ;
/* dump_stack(); */
return - ENOSYS ;
}
EXPORT_SYMBOL_GPL ( wusbhc_rh_suspend ) ;
int wusbhc_rh_resume ( struct usb_hcd * usb_hcd )
{
struct wusbhc * wusbhc = usb_hcd_to_wusbhc ( usb_hcd ) ;
dev_err ( wusbhc - > dev , " %s (%p [%p]) UNIMPLEMENTED \n " , __func__ ,
usb_hcd , wusbhc ) ;
/* dump_stack(); */
return - ENOSYS ;
}
EXPORT_SYMBOL_GPL ( wusbhc_rh_resume ) ;
int wusbhc_rh_start_port_reset ( struct usb_hcd * usb_hcd , unsigned port_idx )
{
struct wusbhc * wusbhc = usb_hcd_to_wusbhc ( usb_hcd ) ;
dev_err ( wusbhc - > dev , " %s (%p [%p], port_idx %u) UNIMPLEMENTED \n " ,
__func__ , usb_hcd , wusbhc , port_idx ) ;
WARN_ON ( 1 ) ;
return - ENOSYS ;
}
EXPORT_SYMBOL_GPL ( wusbhc_rh_start_port_reset ) ;
static void wusb_port_init ( struct wusb_port * port )
{
port - > status | = USB_PORT_STAT_HIGH_SPEED ;
}
/*
* Alloc fake port specific fields and status .
*/
int wusbhc_rh_create ( struct wusbhc * wusbhc )
{
int result = - ENOMEM ;
size_t port_size , itr ;
port_size = wusbhc - > ports_max * sizeof ( wusbhc - > port [ 0 ] ) ;
wusbhc - > port = kzalloc ( port_size , GFP_KERNEL ) ;
if ( wusbhc - > port = = NULL )
goto error_port_alloc ;
for ( itr = 0 ; itr < wusbhc - > ports_max ; itr + + )
wusb_port_init ( & wusbhc - > port [ itr ] ) ;
result = 0 ;
error_port_alloc :
return result ;
}
void wusbhc_rh_destroy ( struct wusbhc * wusbhc )
{
kfree ( wusbhc - > port ) ;
}