2014-07-14 17:57:49 +04:00
/*
* drivers / usb / misc / lvstest . c
*
* Test pattern generation for Link Layer Validation System Tests
*
* Copyright ( C ) 2014 ST Microelectronics
2015-06-26 01:01:08 +03:00
* Pratyush Anand < pratyush . anand @ gmail . com >
2014-07-14 17:57:49 +04:00
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/usb.h>
# include <linux/usb/ch11.h>
# include <linux/usb/hcd.h>
# include <linux/usb/phy.h>
struct lvs_rh {
/* root hub interface */
struct usb_interface * intf ;
/* if lvs device connected */
bool present ;
/* port no at which lvs device is present */
int portnum ;
/* urb buffer */
u8 buffer [ 8 ] ;
/* class descriptor */
struct usb_hub_descriptor descriptor ;
/* urb for polling interrupt pipe */
struct urb * urb ;
/* LVH RH work */
struct work_struct rh_work ;
/* RH port status */
struct usb_port_status port_status ;
} ;
static struct usb_device * create_lvs_device ( struct usb_interface * intf )
{
struct usb_device * udev , * hdev ;
struct usb_hcd * hcd ;
struct lvs_rh * lvs = usb_get_intfdata ( intf ) ;
if ( ! lvs - > present ) {
dev_err ( & intf - > dev , " No LVS device is present \n " ) ;
return NULL ;
}
hdev = interface_to_usbdev ( intf ) ;
hcd = bus_to_hcd ( hdev - > bus ) ;
udev = usb_alloc_dev ( hdev , hdev - > bus , lvs - > portnum ) ;
if ( ! udev ) {
dev_err ( & intf - > dev , " Could not allocate lvs udev \n " ) ;
return NULL ;
}
udev - > speed = USB_SPEED_SUPER ;
udev - > ep0 . desc . wMaxPacketSize = cpu_to_le16 ( 512 ) ;
usb_set_device_state ( udev , USB_STATE_DEFAULT ) ;
if ( hcd - > driver - > enable_device ) {
if ( hcd - > driver - > enable_device ( hcd , udev ) < 0 ) {
dev_err ( & intf - > dev , " Failed to enable \n " ) ;
usb_put_dev ( udev ) ;
return NULL ;
}
}
return udev ;
}
static void destroy_lvs_device ( struct usb_device * udev )
{
struct usb_device * hdev = udev - > parent ;
struct usb_hcd * hcd = bus_to_hcd ( hdev - > bus ) ;
if ( hcd - > driver - > free_dev )
hcd - > driver - > free_dev ( hcd , udev ) ;
usb_put_dev ( udev ) ;
}
static int lvs_rh_clear_port_feature ( struct usb_device * hdev ,
int port1 , int feature )
{
return usb_control_msg ( hdev , usb_sndctrlpipe ( hdev , 0 ) ,
USB_REQ_CLEAR_FEATURE , USB_RT_PORT , feature , port1 ,
NULL , 0 , 1000 ) ;
}
static int lvs_rh_set_port_feature ( struct usb_device * hdev ,
int port1 , int feature )
{
return usb_control_msg ( hdev , usb_sndctrlpipe ( hdev , 0 ) ,
USB_REQ_SET_FEATURE , USB_RT_PORT , feature , port1 ,
NULL , 0 , 1000 ) ;
}
static ssize_t u3_entry_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct usb_interface * intf = to_usb_interface ( dev ) ;
struct usb_device * hdev = interface_to_usbdev ( intf ) ;
struct lvs_rh * lvs = usb_get_intfdata ( intf ) ;
struct usb_device * udev ;
int ret ;
udev = create_lvs_device ( intf ) ;
if ( ! udev ) {
dev_err ( dev , " failed to create lvs device \n " ) ;
return - ENOMEM ;
}
ret = lvs_rh_set_port_feature ( hdev , lvs - > portnum ,
USB_PORT_FEAT_SUSPEND ) ;
if ( ret < 0 )
dev_err ( dev , " can't issue U3 entry %d \n " , ret ) ;
destroy_lvs_device ( udev ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR_WO ( u3_entry ) ;
static ssize_t u3_exit_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct usb_interface * intf = to_usb_interface ( dev ) ;
struct usb_device * hdev = interface_to_usbdev ( intf ) ;
struct lvs_rh * lvs = usb_get_intfdata ( intf ) ;
struct usb_device * udev ;
int ret ;
udev = create_lvs_device ( intf ) ;
if ( ! udev ) {
dev_err ( dev , " failed to create lvs device \n " ) ;
return - ENOMEM ;
}
ret = lvs_rh_clear_port_feature ( hdev , lvs - > portnum ,
USB_PORT_FEAT_SUSPEND ) ;
if ( ret < 0 )
dev_err ( dev , " can't issue U3 exit %d \n " , ret ) ;
destroy_lvs_device ( udev ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR_WO ( u3_exit ) ;
static ssize_t hot_reset_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct usb_interface * intf = to_usb_interface ( dev ) ;
struct usb_device * hdev = interface_to_usbdev ( intf ) ;
struct lvs_rh * lvs = usb_get_intfdata ( intf ) ;
int ret ;
ret = lvs_rh_set_port_feature ( hdev , lvs - > portnum ,
USB_PORT_FEAT_RESET ) ;
if ( ret < 0 ) {
dev_err ( dev , " can't issue hot reset %d \n " , ret ) ;
return ret ;
}
return count ;
}
static DEVICE_ATTR_WO ( hot_reset ) ;
static ssize_t u2_timeout_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct usb_interface * intf = to_usb_interface ( dev ) ;
struct usb_device * hdev = interface_to_usbdev ( intf ) ;
struct lvs_rh * lvs = usb_get_intfdata ( intf ) ;
unsigned long val ;
int ret ;
ret = kstrtoul ( buf , 10 , & val ) ;
if ( ret < 0 ) {
dev_err ( dev , " couldn't parse string %d \n " , ret ) ;
return ret ;
}
if ( val < 0 | | val > 127 )
return - EINVAL ;
ret = lvs_rh_set_port_feature ( hdev , lvs - > portnum | ( val < < 8 ) ,
USB_PORT_FEAT_U2_TIMEOUT ) ;
if ( ret < 0 ) {
dev_err ( dev , " Error %d while setting U2 timeout %ld \n " , ret , val ) ;
return ret ;
}
return count ;
}
static DEVICE_ATTR_WO ( u2_timeout ) ;
static ssize_t u1_timeout_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct usb_interface * intf = to_usb_interface ( dev ) ;
struct usb_device * hdev = interface_to_usbdev ( intf ) ;
struct lvs_rh * lvs = usb_get_intfdata ( intf ) ;
unsigned long val ;
int ret ;
ret = kstrtoul ( buf , 10 , & val ) ;
if ( ret < 0 ) {
dev_err ( dev , " couldn't parse string %d \n " , ret ) ;
return ret ;
}
if ( val < 0 | | val > 127 )
return - EINVAL ;
ret = lvs_rh_set_port_feature ( hdev , lvs - > portnum | ( val < < 8 ) ,
USB_PORT_FEAT_U1_TIMEOUT ) ;
if ( ret < 0 ) {
dev_err ( dev , " Error %d while setting U1 timeout %ld \n " , ret , val ) ;
return ret ;
}
return count ;
}
static DEVICE_ATTR_WO ( u1_timeout ) ;
static ssize_t get_dev_desc_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct usb_interface * intf = to_usb_interface ( dev ) ;
struct usb_device * udev ;
struct usb_device_descriptor * descriptor ;
int ret ;
descriptor = kmalloc ( sizeof ( * descriptor ) , GFP_KERNEL ) ;
2016-08-25 20:39:20 +03:00
if ( ! descriptor )
2014-07-14 17:57:49 +04:00
return - ENOMEM ;
udev = create_lvs_device ( intf ) ;
if ( ! udev ) {
dev_err ( dev , " failed to create lvs device \n " ) ;
ret = - ENOMEM ;
goto free_desc ;
}
ret = usb_control_msg ( udev , ( PIPE_CONTROL < < 30 ) | USB_DIR_IN ,
USB_REQ_GET_DESCRIPTOR , USB_DIR_IN , USB_DT_DEVICE < < 8 ,
0 , descriptor , sizeof ( * descriptor ) ,
USB_CTRL_GET_TIMEOUT ) ;
if ( ret < 0 )
dev_err ( dev , " can't read device descriptor %d \n " , ret ) ;
destroy_lvs_device ( udev ) ;
free_desc :
kfree ( descriptor ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR_WO ( get_dev_desc ) ;
static struct attribute * lvs_attributes [ ] = {
& dev_attr_get_dev_desc . attr ,
& dev_attr_u1_timeout . attr ,
& dev_attr_u2_timeout . attr ,
& dev_attr_hot_reset . attr ,
& dev_attr_u3_entry . attr ,
& dev_attr_u3_exit . attr ,
NULL
} ;
static const struct attribute_group lvs_attr_group = {
. attrs = lvs_attributes ,
} ;
static void lvs_rh_work ( struct work_struct * work )
{
struct lvs_rh * lvs = container_of ( work , struct lvs_rh , rh_work ) ;
struct usb_interface * intf = lvs - > intf ;
struct usb_device * hdev = interface_to_usbdev ( intf ) ;
struct usb_hcd * hcd = bus_to_hcd ( hdev - > bus ) ;
struct usb_hub_descriptor * descriptor = & lvs - > descriptor ;
struct usb_port_status * port_status = & lvs - > port_status ;
int i , ret = 0 ;
u16 portchange ;
/* Examine each root port */
for ( i = 1 ; i < = descriptor - > bNbrPorts ; i + + ) {
ret = usb_control_msg ( hdev , usb_rcvctrlpipe ( hdev , 0 ) ,
USB_REQ_GET_STATUS , USB_DIR_IN | USB_RT_PORT , 0 , i ,
port_status , sizeof ( * port_status ) , 1000 ) ;
if ( ret < 4 )
continue ;
2014-07-21 08:46:53 +04:00
portchange = le16_to_cpu ( port_status - > wPortChange ) ;
2014-07-14 17:57:49 +04:00
if ( portchange & USB_PORT_STAT_C_LINK_STATE )
lvs_rh_clear_port_feature ( hdev , i ,
USB_PORT_FEAT_C_PORT_LINK_STATE ) ;
if ( portchange & USB_PORT_STAT_C_ENABLE )
lvs_rh_clear_port_feature ( hdev , i ,
USB_PORT_FEAT_C_ENABLE ) ;
if ( portchange & USB_PORT_STAT_C_RESET )
lvs_rh_clear_port_feature ( hdev , i ,
USB_PORT_FEAT_C_RESET ) ;
if ( portchange & USB_PORT_STAT_C_BH_RESET )
lvs_rh_clear_port_feature ( hdev , i ,
USB_PORT_FEAT_C_BH_PORT_RESET ) ;
if ( portchange & USB_PORT_STAT_C_CONNECTION ) {
lvs_rh_clear_port_feature ( hdev , i ,
USB_PORT_FEAT_C_CONNECTION ) ;
2014-07-21 08:46:53 +04:00
if ( le16_to_cpu ( port_status - > wPortStatus ) &
2014-07-14 17:57:49 +04:00
USB_PORT_STAT_CONNECTION ) {
lvs - > present = true ;
lvs - > portnum = i ;
2014-09-24 23:05:50 +04:00
if ( hcd - > usb_phy )
usb_phy_notify_connect ( hcd - > usb_phy ,
2014-07-14 17:57:49 +04:00
USB_SPEED_SUPER ) ;
} else {
lvs - > present = false ;
2014-09-24 23:05:50 +04:00
if ( hcd - > usb_phy )
usb_phy_notify_disconnect ( hcd - > usb_phy ,
2014-07-14 17:57:49 +04:00
USB_SPEED_SUPER ) ;
}
break ;
}
}
ret = usb_submit_urb ( lvs - > urb , GFP_KERNEL ) ;
if ( ret ! = 0 & & ret ! = - ENODEV & & ret ! = - EPERM )
dev_err ( & intf - > dev , " urb resubmit error %d \n " , ret ) ;
}
static void lvs_rh_irq ( struct urb * urb )
{
struct lvs_rh * lvs = urb - > context ;
2016-07-28 11:45:11 +03:00
schedule_work ( & lvs - > rh_work ) ;
2014-07-14 17:57:49 +04:00
}
static int lvs_rh_probe ( struct usb_interface * intf ,
const struct usb_device_id * id )
{
struct usb_device * hdev ;
struct usb_host_interface * desc ;
struct usb_endpoint_descriptor * endpoint ;
struct lvs_rh * lvs ;
unsigned int pipe ;
int ret , maxp ;
hdev = interface_to_usbdev ( intf ) ;
desc = intf - > cur_altsetting ;
endpoint = & desc - > endpoint [ 0 ] . desc ;
/* valid only for SS root hub */
if ( hdev - > descriptor . bDeviceProtocol ! = USB_HUB_PR_SS | | hdev - > parent ) {
dev_err ( & intf - > dev , " Bind LVS driver with SS root Hub only \n " ) ;
return - EINVAL ;
}
lvs = devm_kzalloc ( & intf - > dev , sizeof ( * lvs ) , GFP_KERNEL ) ;
if ( ! lvs )
return - ENOMEM ;
lvs - > intf = intf ;
usb_set_intfdata ( intf , lvs ) ;
/* how many number of ports this root hub has */
ret = usb_control_msg ( hdev , usb_rcvctrlpipe ( hdev , 0 ) ,
USB_REQ_GET_DESCRIPTOR , USB_DIR_IN | USB_RT_HUB ,
USB_DT_SS_HUB < < 8 , 0 , & lvs - > descriptor ,
USB_DT_SS_HUB_SIZE , USB_CTRL_GET_TIMEOUT ) ;
if ( ret < ( USB_DT_HUB_NONVAR_SIZE + 2 ) ) {
dev_err ( & hdev - > dev , " wrong root hub descriptor read %d \n " , ret ) ;
return ret ;
}
/* submit urb to poll interrupt endpoint */
lvs - > urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
2016-08-12 00:14:42 +03:00
if ( ! lvs - > urb )
2014-07-14 17:57:49 +04:00
return - ENOMEM ;
INIT_WORK ( & lvs - > rh_work , lvs_rh_work ) ;
ret = sysfs_create_group ( & intf - > dev . kobj , & lvs_attr_group ) ;
if ( ret < 0 ) {
dev_err ( & intf - > dev , " Failed to create sysfs node %d \n " , ret ) ;
2016-07-28 11:45:11 +03:00
goto free_urb ;
2014-07-14 17:57:49 +04:00
}
pipe = usb_rcvintpipe ( hdev , endpoint - > bEndpointAddress ) ;
maxp = usb_maxpacket ( hdev , pipe , usb_pipeout ( pipe ) ) ;
usb_fill_int_urb ( lvs - > urb , hdev , pipe , & lvs - > buffer [ 0 ] , maxp ,
lvs_rh_irq , lvs , endpoint - > bInterval ) ;
ret = usb_submit_urb ( lvs - > urb , GFP_KERNEL ) ;
if ( ret < 0 ) {
dev_err ( & intf - > dev , " couldn't submit lvs urb %d \n " , ret ) ;
goto sysfs_remove ;
}
return ret ;
sysfs_remove :
sysfs_remove_group ( & intf - > dev . kobj , & lvs_attr_group ) ;
free_urb :
usb_free_urb ( lvs - > urb ) ;
return ret ;
}
static void lvs_rh_disconnect ( struct usb_interface * intf )
{
struct lvs_rh * lvs = usb_get_intfdata ( intf ) ;
sysfs_remove_group ( & intf - > dev . kobj , & lvs_attr_group ) ;
2016-07-28 11:45:11 +03:00
flush_work ( & lvs - > rh_work ) ;
2014-07-14 17:57:49 +04:00
usb_free_urb ( lvs - > urb ) ;
}
static struct usb_driver lvs_driver = {
. name = " lvs " ,
. probe = lvs_rh_probe ,
. disconnect = lvs_rh_disconnect ,
} ;
module_usb_driver ( lvs_driver ) ;
MODULE_DESCRIPTION ( " Link Layer Validation System Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;