2021-11-23 14:20:47 +01:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* xen - hcd . c
*
* Xen USB Virtual Host Controller driver
*
* Copyright ( C ) 2009 , FUJITSU LABORATORIES LTD .
* Author : Noboru Iwamatsu < n_iwamatsu @ jp . fujitsu . com >
*/
# include <linux/module.h>
# include <linux/usb.h>
# include <linux/list.h>
# include <linux/usb/hcd.h>
# include <linux/io.h>
# include <xen/xen.h>
# include <xen/xenbus.h>
# include <xen/grant_table.h>
# include <xen/events.h>
# include <xen/page.h>
# include <xen/interface/io/usbif.h>
/* Private per-URB data */
struct urb_priv {
struct list_head list ;
struct urb * urb ;
int req_id ; /* RING_REQUEST id for submitting */
int unlink_req_id ; /* RING_REQUEST id for unlinking */
int status ;
bool unlinked ; /* dequeued marker */
} ;
/* virtual roothub port status */
struct rhport_status {
__u32 status ;
bool resuming ; /* in resuming */
bool c_connection ; /* connection changed */
unsigned long timeout ;
} ;
/* status of attached device */
struct vdevice_status {
int devnum ;
enum usb_device_state status ;
enum usb_device_speed speed ;
} ;
/* RING request shadow */
struct usb_shadow {
struct xenusb_urb_request req ;
struct urb * urb ;
2022-03-11 11:35:09 +01:00
bool in_flight ;
2021-11-23 14:20:47 +01:00
} ;
struct xenhcd_info {
/* Virtual Host Controller has 4 urb queues */
struct list_head pending_submit_list ;
struct list_head pending_unlink_list ;
struct list_head in_progress_list ;
struct list_head giveback_waiting_list ;
spinlock_t lock ;
/* timer that kick pending and giveback waiting urbs */
struct timer_list watchdog ;
unsigned long actions ;
/* virtual root hub */
int rh_numports ;
struct rhport_status ports [ XENUSB_MAX_PORTNR ] ;
struct vdevice_status devices [ XENUSB_MAX_PORTNR ] ;
/* Xen related staff */
struct xenbus_device * xbdev ;
int urb_ring_ref ;
int conn_ring_ref ;
struct xenusb_urb_front_ring urb_ring ;
struct xenusb_conn_front_ring conn_ring ;
unsigned int evtchn ;
unsigned int irq ;
struct usb_shadow shadow [ XENUSB_URB_RING_SIZE ] ;
unsigned int shadow_free ;
bool error ;
} ;
# define XENHCD_RING_JIFFIES (HZ / 200)
# define XENHCD_SCAN_JIFFIES 1
enum xenhcd_timer_action {
TIMER_RING_WATCHDOG ,
TIMER_SCAN_PENDING_URBS ,
} ;
static struct kmem_cache * xenhcd_urbp_cachep ;
static inline struct xenhcd_info * xenhcd_hcd_to_info ( struct usb_hcd * hcd )
{
return ( struct xenhcd_info * ) hcd - > hcd_priv ;
}
static inline struct usb_hcd * xenhcd_info_to_hcd ( struct xenhcd_info * info )
{
return container_of ( ( void * ) info , struct usb_hcd , hcd_priv ) ;
}
static void xenhcd_set_error ( struct xenhcd_info * info , const char * msg )
{
info - > error = true ;
pr_alert ( " xen-hcd: protocol error: %s! \n " , msg ) ;
}
static inline void xenhcd_timer_action_done ( struct xenhcd_info * info ,
enum xenhcd_timer_action action )
{
clear_bit ( action , & info - > actions ) ;
}
static void xenhcd_timer_action ( struct xenhcd_info * info ,
enum xenhcd_timer_action action )
{
if ( timer_pending ( & info - > watchdog ) & &
test_bit ( TIMER_SCAN_PENDING_URBS , & info - > actions ) )
return ;
if ( ! test_and_set_bit ( action , & info - > actions ) ) {
unsigned long t ;
switch ( action ) {
case TIMER_RING_WATCHDOG :
t = XENHCD_RING_JIFFIES ;
break ;
default :
t = XENHCD_SCAN_JIFFIES ;
break ;
}
mod_timer ( & info - > watchdog , t + jiffies ) ;
}
}
/*
* set virtual port connection status
*/
static void xenhcd_set_connect_state ( struct xenhcd_info * info , int portnum )
{
int port ;
port = portnum - 1 ;
if ( info - > ports [ port ] . status & USB_PORT_STAT_POWER ) {
switch ( info - > devices [ port ] . speed ) {
case XENUSB_SPEED_NONE :
info - > ports [ port ] . status & =
~ ( USB_PORT_STAT_CONNECTION |
USB_PORT_STAT_ENABLE |
USB_PORT_STAT_LOW_SPEED |
USB_PORT_STAT_HIGH_SPEED |
USB_PORT_STAT_SUSPEND ) ;
break ;
case XENUSB_SPEED_LOW :
info - > ports [ port ] . status | = USB_PORT_STAT_CONNECTION ;
info - > ports [ port ] . status | = USB_PORT_STAT_LOW_SPEED ;
break ;
case XENUSB_SPEED_FULL :
info - > ports [ port ] . status | = USB_PORT_STAT_CONNECTION ;
break ;
case XENUSB_SPEED_HIGH :
info - > ports [ port ] . status | = USB_PORT_STAT_CONNECTION ;
info - > ports [ port ] . status | = USB_PORT_STAT_HIGH_SPEED ;
break ;
default : /* error */
return ;
}
info - > ports [ port ] . status | = ( USB_PORT_STAT_C_CONNECTION < < 16 ) ;
}
}
/*
* set virtual device connection status
*/
static int xenhcd_rhport_connect ( struct xenhcd_info * info , __u8 portnum ,
__u8 speed )
{
int port ;
if ( portnum < 1 | | portnum > info - > rh_numports )
return - EINVAL ; /* invalid port number */
port = portnum - 1 ;
if ( info - > devices [ port ] . speed ! = speed ) {
switch ( speed ) {
case XENUSB_SPEED_NONE : /* disconnect */
info - > devices [ port ] . status = USB_STATE_NOTATTACHED ;
break ;
case XENUSB_SPEED_LOW :
case XENUSB_SPEED_FULL :
case XENUSB_SPEED_HIGH :
info - > devices [ port ] . status = USB_STATE_ATTACHED ;
break ;
default : /* error */
return - EINVAL ;
}
info - > devices [ port ] . speed = speed ;
info - > ports [ port ] . c_connection = true ;
xenhcd_set_connect_state ( info , portnum ) ;
}
return 0 ;
}
/*
* SetPortFeature ( PORT_SUSPENDED )
*/
static void xenhcd_rhport_suspend ( struct xenhcd_info * info , int portnum )
{
int port ;
port = portnum - 1 ;
info - > ports [ port ] . status | = USB_PORT_STAT_SUSPEND ;
info - > devices [ port ] . status = USB_STATE_SUSPENDED ;
}
/*
* ClearPortFeature ( PORT_SUSPENDED )
*/
static void xenhcd_rhport_resume ( struct xenhcd_info * info , int portnum )
{
int port ;
port = portnum - 1 ;
if ( info - > ports [ port ] . status & USB_PORT_STAT_SUSPEND ) {
info - > ports [ port ] . resuming = true ;
info - > ports [ port ] . timeout = jiffies + msecs_to_jiffies ( 20 ) ;
}
}
/*
* SetPortFeature ( PORT_POWER )
*/
static void xenhcd_rhport_power_on ( struct xenhcd_info * info , int portnum )
{
int port ;
port = portnum - 1 ;
if ( ( info - > ports [ port ] . status & USB_PORT_STAT_POWER ) = = 0 ) {
info - > ports [ port ] . status | = USB_PORT_STAT_POWER ;
if ( info - > devices [ port ] . status ! = USB_STATE_NOTATTACHED )
info - > devices [ port ] . status = USB_STATE_POWERED ;
if ( info - > ports [ port ] . c_connection )
xenhcd_set_connect_state ( info , portnum ) ;
}
}
/*
* ClearPortFeature ( PORT_POWER )
* SetConfiguration ( non - zero )
* Power_Source_Off
* Over - current
*/
static void xenhcd_rhport_power_off ( struct xenhcd_info * info , int portnum )
{
int port ;
port = portnum - 1 ;
if ( info - > ports [ port ] . status & USB_PORT_STAT_POWER ) {
info - > ports [ port ] . status = 0 ;
if ( info - > devices [ port ] . status ! = USB_STATE_NOTATTACHED )
info - > devices [ port ] . status = USB_STATE_ATTACHED ;
}
}
/*
* ClearPortFeature ( PORT_ENABLE )
*/
static void xenhcd_rhport_disable ( struct xenhcd_info * info , int portnum )
{
int port ;
port = portnum - 1 ;
info - > ports [ port ] . status & = ~ USB_PORT_STAT_ENABLE ;
info - > ports [ port ] . status & = ~ USB_PORT_STAT_SUSPEND ;
info - > ports [ port ] . resuming = false ;
if ( info - > devices [ port ] . status ! = USB_STATE_NOTATTACHED )
info - > devices [ port ] . status = USB_STATE_POWERED ;
}
/*
* SetPortFeature ( PORT_RESET )
*/
static void xenhcd_rhport_reset ( struct xenhcd_info * info , int portnum )
{
int port ;
port = portnum - 1 ;
info - > ports [ port ] . status & = ~ ( USB_PORT_STAT_ENABLE |
USB_PORT_STAT_LOW_SPEED |
USB_PORT_STAT_HIGH_SPEED ) ;
info - > ports [ port ] . status | = USB_PORT_STAT_RESET ;
if ( info - > devices [ port ] . status ! = USB_STATE_NOTATTACHED )
info - > devices [ port ] . status = USB_STATE_ATTACHED ;
/* 10msec reset signaling */
info - > ports [ port ] . timeout = jiffies + msecs_to_jiffies ( 10 ) ;
}
# ifdef CONFIG_PM
static int xenhcd_bus_suspend ( struct usb_hcd * hcd )
{
struct xenhcd_info * info = xenhcd_hcd_to_info ( hcd ) ;
int ret = 0 ;
int i , ports ;
ports = info - > rh_numports ;
spin_lock_irq ( & info - > lock ) ;
if ( ! test_bit ( HCD_FLAG_HW_ACCESSIBLE , & hcd - > flags ) ) {
ret = - ESHUTDOWN ;
} else {
/* suspend any active ports*/
for ( i = 1 ; i < = ports ; i + + )
xenhcd_rhport_suspend ( info , i ) ;
}
spin_unlock_irq ( & info - > lock ) ;
del_timer_sync ( & info - > watchdog ) ;
return ret ;
}
static int xenhcd_bus_resume ( struct usb_hcd * hcd )
{
struct xenhcd_info * info = xenhcd_hcd_to_info ( hcd ) ;
int ret = 0 ;
int i , ports ;
ports = info - > rh_numports ;
spin_lock_irq ( & info - > lock ) ;
if ( ! test_bit ( HCD_FLAG_HW_ACCESSIBLE , & hcd - > flags ) ) {
ret = - ESHUTDOWN ;
} else {
/* resume any suspended ports*/
for ( i = 1 ; i < = ports ; i + + )
xenhcd_rhport_resume ( info , i ) ;
}
spin_unlock_irq ( & info - > lock ) ;
return ret ;
}
# endif
static void xenhcd_hub_descriptor ( struct xenhcd_info * info ,
struct usb_hub_descriptor * desc )
{
__u16 temp ;
int ports = info - > rh_numports ;
desc - > bDescriptorType = 0x29 ;
desc - > bPwrOn2PwrGood = 10 ; /* EHCI says 20ms max */
desc - > bHubContrCurrent = 0 ;
desc - > bNbrPorts = ports ;
/* size of DeviceRemovable and PortPwrCtrlMask fields */
temp = 1 + ( ports / 8 ) ;
desc - > bDescLength = 7 + 2 * temp ;
/* bitmaps for DeviceRemovable and PortPwrCtrlMask */
memset ( & desc - > u . hs . DeviceRemovable [ 0 ] , 0 , temp ) ;
memset ( & desc - > u . hs . DeviceRemovable [ temp ] , 0xff , temp ) ;
/* per-port over current reporting and no power switching */
temp = 0x000a ;
desc - > wHubCharacteristics = cpu_to_le16 ( temp ) ;
}
/* port status change mask for hub_status_data */
# define PORT_C_MASK ((USB_PORT_STAT_C_CONNECTION | \
USB_PORT_STAT_C_ENABLE | \
USB_PORT_STAT_C_SUSPEND | \
USB_PORT_STAT_C_OVERCURRENT | \
USB_PORT_STAT_C_RESET ) < < 16 )
/*
* See USB 2.0 Spec , 11.12 .4 Hub and Port Status Change Bitmap .
* If port status changed , writes the bitmap to buf and return
* that length ( number of bytes ) .
* If Nothing changed , return 0.
*/
static int xenhcd_hub_status_data ( struct usb_hcd * hcd , char * buf )
{
struct xenhcd_info * info = xenhcd_hcd_to_info ( hcd ) ;
int ports ;
int i ;
unsigned long flags ;
int ret ;
int changed = 0 ;
/* initialize the status to no-changes */
ports = info - > rh_numports ;
ret = 1 + ( ports / 8 ) ;
memset ( buf , 0 , ret ) ;
spin_lock_irqsave ( & info - > lock , flags ) ;
for ( i = 0 ; i < ports ; i + + ) {
/* check status for each port */
if ( info - > ports [ i ] . status & PORT_C_MASK ) {
buf [ ( i + 1 ) / 8 ] | = 1 < < ( i + 1 ) % 8 ;
changed = 1 ;
}
}
if ( ( hcd - > state = = HC_STATE_SUSPENDED ) & & ( changed = = 1 ) )
usb_hcd_resume_root_hub ( hcd ) ;
spin_unlock_irqrestore ( & info - > lock , flags ) ;
return changed ? ret : 0 ;
}
static int xenhcd_hub_control ( struct usb_hcd * hcd , __u16 typeReq , __u16 wValue ,
__u16 wIndex , char * buf , __u16 wLength )
{
struct xenhcd_info * info = xenhcd_hcd_to_info ( hcd ) ;
int ports = info - > rh_numports ;
unsigned long flags ;
int ret = 0 ;
int i ;
int changed = 0 ;
spin_lock_irqsave ( & info - > lock , flags ) ;
switch ( typeReq ) {
case ClearHubFeature :
/* ignore this request */
break ;
case ClearPortFeature :
if ( ! wIndex | | wIndex > ports )
goto error ;
switch ( wValue ) {
case USB_PORT_FEAT_SUSPEND :
xenhcd_rhport_resume ( info , wIndex ) ;
break ;
case USB_PORT_FEAT_POWER :
xenhcd_rhport_power_off ( info , wIndex ) ;
break ;
case USB_PORT_FEAT_ENABLE :
xenhcd_rhport_disable ( info , wIndex ) ;
break ;
case USB_PORT_FEAT_C_CONNECTION :
info - > ports [ wIndex - 1 ] . c_connection = false ;
fallthrough ;
default :
info - > ports [ wIndex - 1 ] . status & = ~ ( 1 < < wValue ) ;
break ;
}
break ;
case GetHubDescriptor :
xenhcd_hub_descriptor ( info , ( struct usb_hub_descriptor * ) buf ) ;
break ;
case GetHubStatus :
/* always local power supply good and no over-current exists. */
* ( __le32 * ) buf = cpu_to_le32 ( 0 ) ;
break ;
case GetPortStatus :
if ( ! wIndex | | wIndex > ports )
goto error ;
wIndex - - ;
/* resume completion */
if ( info - > ports [ wIndex ] . resuming & &
time_after_eq ( jiffies , info - > ports [ wIndex ] . timeout ) ) {
info - > ports [ wIndex ] . status | =
USB_PORT_STAT_C_SUSPEND < < 16 ;
info - > ports [ wIndex ] . status & = ~ USB_PORT_STAT_SUSPEND ;
}
/* reset completion */
if ( ( info - > ports [ wIndex ] . status & USB_PORT_STAT_RESET ) ! = 0 & &
time_after_eq ( jiffies , info - > ports [ wIndex ] . timeout ) ) {
info - > ports [ wIndex ] . status | =
USB_PORT_STAT_C_RESET < < 16 ;
info - > ports [ wIndex ] . status & = ~ USB_PORT_STAT_RESET ;
if ( info - > devices [ wIndex ] . status ! =
USB_STATE_NOTATTACHED ) {
info - > ports [ wIndex ] . status | =
USB_PORT_STAT_ENABLE ;
info - > devices [ wIndex ] . status =
USB_STATE_DEFAULT ;
}
switch ( info - > devices [ wIndex ] . speed ) {
case XENUSB_SPEED_LOW :
info - > ports [ wIndex ] . status | =
USB_PORT_STAT_LOW_SPEED ;
break ;
case XENUSB_SPEED_HIGH :
info - > ports [ wIndex ] . status | =
USB_PORT_STAT_HIGH_SPEED ;
break ;
default :
break ;
}
}
* ( __le32 * ) buf = cpu_to_le32 ( info - > ports [ wIndex ] . status ) ;
break ;
case SetPortFeature :
if ( ! wIndex | | wIndex > ports )
goto error ;
switch ( wValue ) {
case USB_PORT_FEAT_POWER :
xenhcd_rhport_power_on ( info , wIndex ) ;
break ;
case USB_PORT_FEAT_RESET :
xenhcd_rhport_reset ( info , wIndex ) ;
break ;
case USB_PORT_FEAT_SUSPEND :
xenhcd_rhport_suspend ( info , wIndex ) ;
break ;
default :
if ( info - > ports [ wIndex - 1 ] . status & USB_PORT_STAT_POWER )
info - > ports [ wIndex - 1 ] . status | = ( 1 < < wValue ) ;
}
break ;
case SetHubFeature :
/* not supported */
default :
error :
ret = - EPIPE ;
}
spin_unlock_irqrestore ( & info - > lock , flags ) ;
/* check status for each port */
for ( i = 0 ; i < ports ; i + + ) {
if ( info - > ports [ i ] . status & PORT_C_MASK )
changed = 1 ;
}
if ( changed )
usb_hcd_poll_rh_status ( hcd ) ;
return ret ;
}
static void xenhcd_free_urb_priv ( struct urb_priv * urbp )
{
urbp - > urb - > hcpriv = NULL ;
kmem_cache_free ( xenhcd_urbp_cachep , urbp ) ;
}
static inline unsigned int xenhcd_get_id_from_freelist ( struct xenhcd_info * info )
{
unsigned int free ;
free = info - > shadow_free ;
info - > shadow_free = info - > shadow [ free ] . req . id ;
info - > shadow [ free ] . req . id = 0x0fff ; /* debug */
return free ;
}
static inline void xenhcd_add_id_to_freelist ( struct xenhcd_info * info ,
unsigned int id )
{
info - > shadow [ id ] . req . id = info - > shadow_free ;
info - > shadow [ id ] . urb = NULL ;
info - > shadow_free = id ;
}
static inline int xenhcd_count_pages ( void * addr , int length )
{
unsigned long vaddr = ( unsigned long ) addr ;
return PFN_UP ( vaddr + length ) - PFN_DOWN ( vaddr ) ;
}
static void xenhcd_gnttab_map ( struct xenhcd_info * info , void * addr , int length ,
grant_ref_t * gref_head ,
struct xenusb_request_segment * seg ,
int nr_pages , int flags )
{
grant_ref_t ref ;
unsigned int offset ;
unsigned int len = length ;
unsigned int bytes ;
int i ;
for ( i = 0 ; i < nr_pages ; i + + ) {
offset = offset_in_page ( addr ) ;
bytes = PAGE_SIZE - offset ;
if ( bytes > len )
bytes = len ;
ref = gnttab_claim_grant_reference ( gref_head ) ;
gnttab_grant_foreign_access_ref ( ref , info - > xbdev - > otherend_id ,
2022-03-11 11:35:00 +01:00
virt_to_gfn ( addr ) , flags ) ;
2021-11-23 14:20:47 +01:00
seg [ i ] . gref = ref ;
seg [ i ] . offset = ( __u16 ) offset ;
seg [ i ] . length = ( __u16 ) bytes ;
addr + = bytes ;
len - = bytes ;
}
}
static __u32 xenhcd_pipe_urb_to_xenusb ( __u32 urb_pipe , __u8 port )
{
static __u32 pipe ;
pipe = usb_pipedevice ( urb_pipe ) < < XENUSB_PIPE_DEV_SHIFT ;
pipe | = usb_pipeendpoint ( urb_pipe ) < < XENUSB_PIPE_EP_SHIFT ;
if ( usb_pipein ( urb_pipe ) )
pipe | = XENUSB_PIPE_DIR ;
switch ( usb_pipetype ( urb_pipe ) ) {
case PIPE_ISOCHRONOUS :
pipe | = XENUSB_PIPE_TYPE_ISOC < < XENUSB_PIPE_TYPE_SHIFT ;
break ;
case PIPE_INTERRUPT :
pipe | = XENUSB_PIPE_TYPE_INT < < XENUSB_PIPE_TYPE_SHIFT ;
break ;
case PIPE_CONTROL :
pipe | = XENUSB_PIPE_TYPE_CTRL < < XENUSB_PIPE_TYPE_SHIFT ;
break ;
case PIPE_BULK :
pipe | = XENUSB_PIPE_TYPE_BULK < < XENUSB_PIPE_TYPE_SHIFT ;
break ;
}
pipe = xenusb_setportnum_pipe ( pipe , port ) ;
return pipe ;
}
static int xenhcd_map_urb_for_request ( struct xenhcd_info * info , struct urb * urb ,
struct xenusb_urb_request * req )
{
grant_ref_t gref_head ;
int nr_buff_pages = 0 ;
int nr_isodesc_pages = 0 ;
int nr_grants = 0 ;
if ( urb - > transfer_buffer_length ) {
nr_buff_pages = xenhcd_count_pages ( urb - > transfer_buffer ,
urb - > transfer_buffer_length ) ;
if ( usb_pipeisoc ( urb - > pipe ) )
nr_isodesc_pages = xenhcd_count_pages (
& urb - > iso_frame_desc [ 0 ] ,
sizeof ( struct usb_iso_packet_descriptor ) *
urb - > number_of_packets ) ;
nr_grants = nr_buff_pages + nr_isodesc_pages ;
if ( nr_grants > XENUSB_MAX_SEGMENTS_PER_REQUEST ) {
pr_err ( " xenhcd: error: %d grants \n " , nr_grants ) ;
return - E2BIG ;
}
if ( gnttab_alloc_grant_references ( nr_grants , & gref_head ) ) {
pr_err ( " xenhcd: gnttab_alloc_grant_references() error \n " ) ;
return - ENOMEM ;
}
xenhcd_gnttab_map ( info , urb - > transfer_buffer ,
urb - > transfer_buffer_length , & gref_head ,
& req - > seg [ 0 ] , nr_buff_pages ,
usb_pipein ( urb - > pipe ) ? 0 : GTF_readonly ) ;
}
req - > pipe = xenhcd_pipe_urb_to_xenusb ( urb - > pipe , urb - > dev - > portnum ) ;
req - > transfer_flags = 0 ;
if ( urb - > transfer_flags & URB_SHORT_NOT_OK )
req - > transfer_flags | = XENUSB_SHORT_NOT_OK ;
req - > buffer_length = urb - > transfer_buffer_length ;
req - > nr_buffer_segs = nr_buff_pages ;
switch ( usb_pipetype ( urb - > pipe ) ) {
case PIPE_ISOCHRONOUS :
req - > u . isoc . interval = urb - > interval ;
req - > u . isoc . start_frame = urb - > start_frame ;
req - > u . isoc . number_of_packets = urb - > number_of_packets ;
req - > u . isoc . nr_frame_desc_segs = nr_isodesc_pages ;
xenhcd_gnttab_map ( info , & urb - > iso_frame_desc [ 0 ] ,
sizeof ( struct usb_iso_packet_descriptor ) *
urb - > number_of_packets ,
& gref_head , & req - > seg [ nr_buff_pages ] ,
nr_isodesc_pages , 0 ) ;
break ;
case PIPE_INTERRUPT :
req - > u . intr . interval = urb - > interval ;
break ;
case PIPE_CONTROL :
if ( urb - > setup_packet )
memcpy ( req - > u . ctrl , urb - > setup_packet , 8 ) ;
break ;
case PIPE_BULK :
break ;
default :
break ;
}
if ( nr_grants )
gnttab_free_grant_references ( gref_head ) ;
return 0 ;
}
2022-03-07 09:48:55 +01:00
static void xenhcd_gnttab_done ( struct xenhcd_info * info , unsigned int id )
2021-11-23 14:20:47 +01:00
{
2022-03-07 09:48:55 +01:00
struct usb_shadow * shadow = info - > shadow + id ;
2021-11-23 14:20:47 +01:00
int nr_segs = 0 ;
int i ;
2022-03-11 11:35:09 +01:00
if ( ! shadow - > in_flight ) {
xenhcd_set_error ( info , " Illegal request id " ) ;
return ;
}
shadow - > in_flight = false ;
2021-11-23 14:20:47 +01:00
nr_segs = shadow - > req . nr_buffer_segs ;
if ( xenusb_pipeisoc ( shadow - > req . pipe ) )
nr_segs + = shadow - > req . u . isoc . nr_frame_desc_segs ;
2022-03-07 09:48:55 +01:00
for ( i = 0 ; i < nr_segs ; i + + ) {
if ( ! gnttab_try_end_foreign_access ( shadow - > req . seg [ i ] . gref ) )
xenhcd_set_error ( info , " backend didn't release grant " ) ;
}
2021-11-23 14:20:47 +01:00
shadow - > req . nr_buffer_segs = 0 ;
shadow - > req . u . isoc . nr_frame_desc_segs = 0 ;
}
static int xenhcd_translate_status ( int status )
{
switch ( status ) {
case XENUSB_STATUS_OK :
return 0 ;
case XENUSB_STATUS_NODEV :
return - ENODEV ;
case XENUSB_STATUS_INVAL :
return - EINVAL ;
case XENUSB_STATUS_STALL :
return - EPIPE ;
case XENUSB_STATUS_IOERROR :
return - EPROTO ;
case XENUSB_STATUS_BABBLE :
return - EOVERFLOW ;
default :
return - ESHUTDOWN ;
}
}
static void xenhcd_giveback_urb ( struct xenhcd_info * info , struct urb * urb ,
int status )
{
struct urb_priv * urbp = ( struct urb_priv * ) urb - > hcpriv ;
int priv_status = urbp - > status ;
list_del_init ( & urbp - > list ) ;
xenhcd_free_urb_priv ( urbp ) ;
if ( urb - > status = = - EINPROGRESS )
urb - > status = xenhcd_translate_status ( status ) ;
spin_unlock ( & info - > lock ) ;
usb_hcd_giveback_urb ( xenhcd_info_to_hcd ( info ) , urb ,
priv_status < = 0 ? priv_status : urb - > status ) ;
spin_lock ( & info - > lock ) ;
}
static int xenhcd_do_request ( struct xenhcd_info * info , struct urb_priv * urbp )
{
struct xenusb_urb_request * req ;
struct urb * urb = urbp - > urb ;
unsigned int id ;
int notify ;
int ret ;
id = xenhcd_get_id_from_freelist ( info ) ;
req = & info - > shadow [ id ] . req ;
req - > id = id ;
if ( unlikely ( urbp - > unlinked ) ) {
req - > u . unlink . unlink_id = urbp - > req_id ;
req - > pipe = xenusb_setunlink_pipe ( xenhcd_pipe_urb_to_xenusb (
urb - > pipe , urb - > dev - > portnum ) ) ;
urbp - > unlink_req_id = id ;
} else {
ret = xenhcd_map_urb_for_request ( info , urb , req ) ;
if ( ret ) {
xenhcd_add_id_to_freelist ( info , id ) ;
return ret ;
}
urbp - > req_id = id ;
}
req = RING_GET_REQUEST ( & info - > urb_ring , info - > urb_ring . req_prod_pvt ) ;
* req = info - > shadow [ id ] . req ;
info - > urb_ring . req_prod_pvt + + ;
info - > shadow [ id ] . urb = urb ;
2022-03-11 11:35:09 +01:00
info - > shadow [ id ] . in_flight = true ;
2021-11-23 14:20:47 +01:00
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY ( & info - > urb_ring , notify ) ;
if ( notify )
notify_remote_via_irq ( info - > irq ) ;
return 0 ;
}
static void xenhcd_kick_pending_urbs ( struct xenhcd_info * info )
{
struct urb_priv * urbp ;
while ( ! list_empty ( & info - > pending_submit_list ) ) {
if ( RING_FULL ( & info - > urb_ring ) ) {
xenhcd_timer_action ( info , TIMER_RING_WATCHDOG ) ;
return ;
}
urbp = list_entry ( info - > pending_submit_list . next ,
struct urb_priv , list ) ;
if ( ! xenhcd_do_request ( info , urbp ) )
list_move_tail ( & urbp - > list , & info - > in_progress_list ) ;
else
xenhcd_giveback_urb ( info , urbp - > urb , - ESHUTDOWN ) ;
}
xenhcd_timer_action_done ( info , TIMER_SCAN_PENDING_URBS ) ;
}
/*
* caller must lock info - > lock
*/
static void xenhcd_cancel_all_enqueued_urbs ( struct xenhcd_info * info )
{
struct urb_priv * urbp , * tmp ;
int req_id ;
list_for_each_entry_safe ( urbp , tmp , & info - > in_progress_list , list ) {
req_id = urbp - > req_id ;
if ( ! urbp - > unlinked ) {
2022-03-07 09:48:55 +01:00
xenhcd_gnttab_done ( info , req_id ) ;
if ( info - > error )
return ;
2021-11-23 14:20:47 +01:00
if ( urbp - > urb - > status = = - EINPROGRESS )
/* not dequeued */
xenhcd_giveback_urb ( info , urbp - > urb ,
- ESHUTDOWN ) ;
else /* dequeued */
xenhcd_giveback_urb ( info , urbp - > urb ,
urbp - > urb - > status ) ;
}
info - > shadow [ req_id ] . urb = NULL ;
}
list_for_each_entry_safe ( urbp , tmp , & info - > pending_submit_list , list )
xenhcd_giveback_urb ( info , urbp - > urb , - ESHUTDOWN ) ;
}
/*
* caller must lock info - > lock
*/
static void xenhcd_giveback_unlinked_urbs ( struct xenhcd_info * info )
{
struct urb_priv * urbp , * tmp ;
list_for_each_entry_safe ( urbp , tmp , & info - > giveback_waiting_list , list )
xenhcd_giveback_urb ( info , urbp - > urb , urbp - > urb - > status ) ;
}
static int xenhcd_submit_urb ( struct xenhcd_info * info , struct urb_priv * urbp )
{
int ret ;
if ( RING_FULL ( & info - > urb_ring ) ) {
list_add_tail ( & urbp - > list , & info - > pending_submit_list ) ;
xenhcd_timer_action ( info , TIMER_RING_WATCHDOG ) ;
return 0 ;
}
if ( ! list_empty ( & info - > pending_submit_list ) ) {
list_add_tail ( & urbp - > list , & info - > pending_submit_list ) ;
xenhcd_timer_action ( info , TIMER_SCAN_PENDING_URBS ) ;
return 0 ;
}
ret = xenhcd_do_request ( info , urbp ) ;
if ( ret = = 0 )
list_add_tail ( & urbp - > list , & info - > in_progress_list ) ;
return ret ;
}
static int xenhcd_unlink_urb ( struct xenhcd_info * info , struct urb_priv * urbp )
{
int ret ;
/* already unlinked? */
if ( urbp - > unlinked )
return - EBUSY ;
urbp - > unlinked = true ;
/* the urb is still in pending_submit queue */
if ( urbp - > req_id = = ~ 0 ) {
list_move_tail ( & urbp - > list , & info - > giveback_waiting_list ) ;
xenhcd_timer_action ( info , TIMER_SCAN_PENDING_URBS ) ;
return 0 ;
}
/* send unlink request to backend */
if ( RING_FULL ( & info - > urb_ring ) ) {
list_move_tail ( & urbp - > list , & info - > pending_unlink_list ) ;
xenhcd_timer_action ( info , TIMER_RING_WATCHDOG ) ;
return 0 ;
}
if ( ! list_empty ( & info - > pending_unlink_list ) ) {
list_move_tail ( & urbp - > list , & info - > pending_unlink_list ) ;
xenhcd_timer_action ( info , TIMER_SCAN_PENDING_URBS ) ;
return 0 ;
}
ret = xenhcd_do_request ( info , urbp ) ;
if ( ret = = 0 )
list_move_tail ( & urbp - > list , & info - > in_progress_list ) ;
return ret ;
}
2022-03-11 11:35:09 +01:00
static void xenhcd_res_to_urb ( struct xenhcd_info * info ,
struct xenusb_urb_response * res , struct urb * urb )
{
if ( unlikely ( ! urb ) )
return ;
if ( res - > actual_length > urb - > transfer_buffer_length )
urb - > actual_length = urb - > transfer_buffer_length ;
else if ( res - > actual_length < 0 )
urb - > actual_length = 0 ;
else
urb - > actual_length = res - > actual_length ;
urb - > error_count = res - > error_count ;
urb - > start_frame = res - > start_frame ;
xenhcd_giveback_urb ( info , urb , res - > status ) ;
}
static int xenhcd_urb_request_done ( struct xenhcd_info * info ,
unsigned int * eoiflag )
2021-11-23 14:20:47 +01:00
{
struct xenusb_urb_response res ;
RING_IDX i , rp ;
__u16 id ;
int more_to_do = 0 ;
unsigned long flags ;
spin_lock_irqsave ( & info - > lock , flags ) ;
rp = info - > urb_ring . sring - > rsp_prod ;
if ( RING_RESPONSE_PROD_OVERFLOW ( & info - > urb_ring , rp ) ) {
xenhcd_set_error ( info , " Illegal index on urb-ring " ) ;
2022-03-07 09:48:55 +01:00
goto err ;
2021-11-23 14:20:47 +01:00
}
rmb ( ) ; /* ensure we see queued responses up to "rp" */
for ( i = info - > urb_ring . rsp_cons ; i ! = rp ; i + + ) {
RING_COPY_RESPONSE ( & info - > urb_ring , i , & res ) ;
id = res . id ;
if ( id > = XENUSB_URB_RING_SIZE ) {
xenhcd_set_error ( info , " Illegal data on urb-ring " ) ;
2022-03-07 09:48:55 +01:00
goto err ;
2021-11-23 14:20:47 +01:00
}
if ( likely ( xenusb_pipesubmit ( info - > shadow [ id ] . req . pipe ) ) ) {
2022-03-07 09:48:55 +01:00
xenhcd_gnttab_done ( info , id ) ;
if ( info - > error )
goto err ;
2022-03-11 11:35:09 +01:00
xenhcd_res_to_urb ( info , & res , info - > shadow [ id ] . urb ) ;
2021-11-23 14:20:47 +01:00
}
xenhcd_add_id_to_freelist ( info , id ) ;
2022-03-11 11:35:09 +01:00
* eoiflag = 0 ;
2021-11-23 14:20:47 +01:00
}
info - > urb_ring . rsp_cons = i ;
if ( i ! = info - > urb_ring . req_prod_pvt )
RING_FINAL_CHECK_FOR_RESPONSES ( & info - > urb_ring , more_to_do ) ;
else
info - > urb_ring . sring - > rsp_event = i + 1 ;
spin_unlock_irqrestore ( & info - > lock , flags ) ;
return more_to_do ;
2022-03-07 09:48:55 +01:00
err :
spin_unlock_irqrestore ( & info - > lock , flags ) ;
return 0 ;
2021-11-23 14:20:47 +01:00
}
2022-03-11 11:35:09 +01:00
static int xenhcd_conn_notify ( struct xenhcd_info * info , unsigned int * eoiflag )
2021-11-23 14:20:47 +01:00
{
struct xenusb_conn_response res ;
struct xenusb_conn_request * req ;
RING_IDX rc , rp ;
__u16 id ;
__u8 portnum , speed ;
int more_to_do = 0 ;
int notify ;
int port_changed = 0 ;
unsigned long flags ;
spin_lock_irqsave ( & info - > lock , flags ) ;
rc = info - > conn_ring . rsp_cons ;
rp = info - > conn_ring . sring - > rsp_prod ;
if ( RING_RESPONSE_PROD_OVERFLOW ( & info - > conn_ring , rp ) ) {
xenhcd_set_error ( info , " Illegal index on conn-ring " ) ;
2021-12-15 11:58:05 +08:00
spin_unlock_irqrestore ( & info - > lock , flags ) ;
2021-11-23 14:20:47 +01:00
return 0 ;
}
rmb ( ) ; /* ensure we see queued responses up to "rp" */
while ( rc ! = rp ) {
RING_COPY_RESPONSE ( & info - > conn_ring , rc , & res ) ;
id = res . id ;
portnum = res . portnum ;
speed = res . speed ;
info - > conn_ring . rsp_cons = + + rc ;
if ( xenhcd_rhport_connect ( info , portnum , speed ) ) {
xenhcd_set_error ( info , " Illegal data on conn-ring " ) ;
2021-12-15 11:58:05 +08:00
spin_unlock_irqrestore ( & info - > lock , flags ) ;
2021-11-23 14:20:47 +01:00
return 0 ;
}
if ( info - > ports [ portnum - 1 ] . c_connection )
port_changed = 1 ;
barrier ( ) ;
req = RING_GET_REQUEST ( & info - > conn_ring ,
info - > conn_ring . req_prod_pvt ) ;
req - > id = id ;
info - > conn_ring . req_prod_pvt + + ;
2022-03-11 11:35:09 +01:00
* eoiflag = 0 ;
2021-11-23 14:20:47 +01:00
}
if ( rc ! = info - > conn_ring . req_prod_pvt )
RING_FINAL_CHECK_FOR_RESPONSES ( & info - > conn_ring , more_to_do ) ;
else
info - > conn_ring . sring - > rsp_event = rc + 1 ;
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY ( & info - > conn_ring , notify ) ;
if ( notify )
notify_remote_via_irq ( info - > irq ) ;
spin_unlock_irqrestore ( & info - > lock , flags ) ;
if ( port_changed )
usb_hcd_poll_rh_status ( xenhcd_info_to_hcd ( info ) ) ;
return more_to_do ;
}
static irqreturn_t xenhcd_int ( int irq , void * dev_id )
{
struct xenhcd_info * info = ( struct xenhcd_info * ) dev_id ;
2022-03-11 11:35:09 +01:00
unsigned int eoiflag = XEN_EOI_FLAG_SPURIOUS ;
2021-11-23 14:20:47 +01:00
2022-03-11 11:35:09 +01:00
if ( unlikely ( info - > error ) ) {
xen_irq_lateeoi ( irq , XEN_EOI_FLAG_SPURIOUS ) ;
2021-11-23 14:20:47 +01:00
return IRQ_HANDLED ;
2022-03-11 11:35:09 +01:00
}
2021-11-23 14:20:47 +01:00
2022-03-11 11:35:09 +01:00
while ( xenhcd_urb_request_done ( info , & eoiflag ) |
xenhcd_conn_notify ( info , & eoiflag ) )
2021-11-23 14:20:47 +01:00
/* Yield point for this unbounded loop. */
cond_resched ( ) ;
2022-03-11 11:35:09 +01:00
xen_irq_lateeoi ( irq , eoiflag ) ;
2021-11-23 14:20:47 +01:00
return IRQ_HANDLED ;
}
static void xenhcd_destroy_rings ( struct xenhcd_info * info )
{
if ( info - > irq )
unbind_from_irqhandler ( info - > irq , info ) ;
info - > irq = 0 ;
2022-04-28 09:01:05 +02:00
xenbus_teardown_ring ( ( void * * ) & info - > urb_ring . sring , 1 ,
& info - > urb_ring_ref ) ;
xenbus_teardown_ring ( ( void * * ) & info - > conn_ring . sring , 1 ,
& info - > conn_ring_ref ) ;
2021-11-23 14:20:47 +01:00
}
static int xenhcd_setup_rings ( struct xenbus_device * dev ,
struct xenhcd_info * info )
{
struct xenusb_urb_sring * urb_sring ;
struct xenusb_conn_sring * conn_sring ;
int err ;
2022-04-28 09:01:02 +02:00
info - > conn_ring_ref = INVALID_GRANT_REF ;
2022-04-28 09:01:05 +02:00
err = xenbus_setup_ring ( dev , GFP_NOIO | __GFP_HIGH ,
( void * * ) & urb_sring , 1 , & info - > urb_ring_ref ) ;
if ( err ) {
xenbus_dev_fatal ( dev , err , " allocating urb ring " ) ;
return err ;
2021-11-23 14:20:47 +01:00
}
2022-04-28 09:01:05 +02:00
XEN_FRONT_RING_INIT ( & info - > urb_ring , urb_sring , PAGE_SIZE ) ;
2021-11-23 14:20:47 +01:00
2022-04-28 09:01:05 +02:00
err = xenbus_setup_ring ( dev , GFP_NOIO | __GFP_HIGH ,
( void * * ) & conn_sring , 1 , & info - > conn_ring_ref ) ;
if ( err ) {
xenbus_dev_fatal ( dev , err , " allocating conn ring " ) ;
2021-11-23 14:20:47 +01:00
goto fail ;
}
2022-04-28 09:01:05 +02:00
XEN_FRONT_RING_INIT ( & info - > conn_ring , conn_sring , PAGE_SIZE ) ;
2021-11-23 14:20:47 +01:00
err = xenbus_alloc_evtchn ( dev , & info - > evtchn ) ;
if ( err ) {
xenbus_dev_fatal ( dev , err , " xenbus_alloc_evtchn " ) ;
goto fail ;
}
2022-03-11 11:35:09 +01:00
err = bind_evtchn_to_irq_lateeoi ( info - > evtchn ) ;
2021-11-23 14:20:47 +01:00
if ( err < = 0 ) {
2022-03-11 11:35:09 +01:00
xenbus_dev_fatal ( dev , err , " bind_evtchn_to_irq_lateeoi " ) ;
2021-11-23 14:20:47 +01:00
goto fail ;
}
info - > irq = err ;
err = request_threaded_irq ( info - > irq , NULL , xenhcd_int ,
IRQF_ONESHOT , " xenhcd " , info ) ;
if ( err ) {
xenbus_dev_fatal ( dev , err , " request_threaded_irq " ) ;
goto free_irq ;
}
return 0 ;
free_irq :
unbind_from_irqhandler ( info - > irq , info ) ;
fail :
xenhcd_destroy_rings ( info ) ;
return err ;
}
static int xenhcd_talk_to_backend ( struct xenbus_device * dev ,
struct xenhcd_info * info )
{
const char * message ;
struct xenbus_transaction xbt ;
int err ;
err = xenhcd_setup_rings ( dev , info ) ;
if ( err )
return err ;
again :
err = xenbus_transaction_start ( & xbt ) ;
if ( err ) {
xenbus_dev_fatal ( dev , err , " starting transaction " ) ;
goto destroy_ring ;
}
err = xenbus_printf ( xbt , dev - > nodename , " urb-ring-ref " , " %u " ,
info - > urb_ring_ref ) ;
if ( err ) {
message = " writing urb-ring-ref " ;
goto abort_transaction ;
}
err = xenbus_printf ( xbt , dev - > nodename , " conn-ring-ref " , " %u " ,
info - > conn_ring_ref ) ;
if ( err ) {
message = " writing conn-ring-ref " ;
goto abort_transaction ;
}
err = xenbus_printf ( xbt , dev - > nodename , " event-channel " , " %u " ,
info - > evtchn ) ;
if ( err ) {
message = " writing event-channel " ;
goto abort_transaction ;
}
err = xenbus_transaction_end ( xbt , 0 ) ;
if ( err ) {
if ( err = = - EAGAIN )
goto again ;
xenbus_dev_fatal ( dev , err , " completing transaction " ) ;
goto destroy_ring ;
}
return 0 ;
abort_transaction :
xenbus_transaction_end ( xbt , 1 ) ;
xenbus_dev_fatal ( dev , err , " %s " , message ) ;
destroy_ring :
xenhcd_destroy_rings ( info ) ;
return err ;
}
static int xenhcd_connect ( struct xenbus_device * dev )
{
struct xenhcd_info * info = dev_get_drvdata ( & dev - > dev ) ;
struct xenusb_conn_request * req ;
int idx , err ;
int notify ;
char name [ TASK_COMM_LEN ] ;
struct usb_hcd * hcd ;
hcd = xenhcd_info_to_hcd ( info ) ;
snprintf ( name , TASK_COMM_LEN , " xenhcd.%d " , hcd - > self . busnum ) ;
err = xenhcd_talk_to_backend ( dev , info ) ;
if ( err )
return err ;
/* prepare ring for hotplug notification */
for ( idx = 0 ; idx < XENUSB_CONN_RING_SIZE ; idx + + ) {
req = RING_GET_REQUEST ( & info - > conn_ring , idx ) ;
req - > id = idx ;
}
info - > conn_ring . req_prod_pvt = idx ;
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY ( & info - > conn_ring , notify ) ;
if ( notify )
notify_remote_via_irq ( info - > irq ) ;
return 0 ;
}
static void xenhcd_disconnect ( struct xenbus_device * dev )
{
struct xenhcd_info * info = dev_get_drvdata ( & dev - > dev ) ;
struct usb_hcd * hcd = xenhcd_info_to_hcd ( info ) ;
usb_remove_hcd ( hcd ) ;
xenbus_frontend_closed ( dev ) ;
}
static void xenhcd_watchdog ( struct timer_list * timer )
{
struct xenhcd_info * info = from_timer ( info , timer , watchdog ) ;
unsigned long flags ;
spin_lock_irqsave ( & info - > lock , flags ) ;
if ( likely ( HC_IS_RUNNING ( xenhcd_info_to_hcd ( info ) - > state ) ) ) {
xenhcd_timer_action_done ( info , TIMER_RING_WATCHDOG ) ;
xenhcd_giveback_unlinked_urbs ( info ) ;
xenhcd_kick_pending_urbs ( info ) ;
}
spin_unlock_irqrestore ( & info - > lock , flags ) ;
}
/*
* one - time HC init
*/
static int xenhcd_setup ( struct usb_hcd * hcd )
{
struct xenhcd_info * info = xenhcd_hcd_to_info ( hcd ) ;
spin_lock_init ( & info - > lock ) ;
INIT_LIST_HEAD ( & info - > pending_submit_list ) ;
INIT_LIST_HEAD ( & info - > pending_unlink_list ) ;
INIT_LIST_HEAD ( & info - > in_progress_list ) ;
INIT_LIST_HEAD ( & info - > giveback_waiting_list ) ;
timer_setup ( & info - > watchdog , xenhcd_watchdog , 0 ) ;
hcd - > has_tt = ( hcd - > driver - > flags & HCD_MASK ) ! = HCD_USB11 ;
return 0 ;
}
/*
* start HC running
*/
static int xenhcd_run ( struct usb_hcd * hcd )
{
hcd - > uses_new_polling = 1 ;
clear_bit ( HCD_FLAG_POLL_RH , & hcd - > flags ) ;
hcd - > state = HC_STATE_RUNNING ;
return 0 ;
}
/*
* stop running HC
*/
static void xenhcd_stop ( struct usb_hcd * hcd )
{
struct xenhcd_info * info = xenhcd_hcd_to_info ( hcd ) ;
del_timer_sync ( & info - > watchdog ) ;
spin_lock_irq ( & info - > lock ) ;
/* cancel all urbs */
hcd - > state = HC_STATE_HALT ;
xenhcd_cancel_all_enqueued_urbs ( info ) ;
xenhcd_giveback_unlinked_urbs ( info ) ;
spin_unlock_irq ( & info - > lock ) ;
}
/*
* called as . urb_enqueue ( )
* non - error returns are promise to giveback the urb later
*/
static int xenhcd_urb_enqueue ( struct usb_hcd * hcd , struct urb * urb ,
gfp_t mem_flags )
{
struct xenhcd_info * info = xenhcd_hcd_to_info ( hcd ) ;
struct urb_priv * urbp ;
unsigned long flags ;
int ret ;
if ( unlikely ( info - > error ) )
return - ESHUTDOWN ;
urbp = kmem_cache_zalloc ( xenhcd_urbp_cachep , mem_flags ) ;
if ( ! urbp )
return - ENOMEM ;
spin_lock_irqsave ( & info - > lock , flags ) ;
urbp - > urb = urb ;
urb - > hcpriv = urbp ;
urbp - > req_id = ~ 0 ;
urbp - > unlink_req_id = ~ 0 ;
INIT_LIST_HEAD ( & urbp - > list ) ;
urbp - > status = 1 ;
urb - > unlinked = false ;
ret = xenhcd_submit_urb ( info , urbp ) ;
if ( ret )
xenhcd_free_urb_priv ( urbp ) ;
spin_unlock_irqrestore ( & info - > lock , flags ) ;
return ret ;
}
/*
* called as . urb_dequeue ( )
*/
static int xenhcd_urb_dequeue ( struct usb_hcd * hcd , struct urb * urb , int status )
{
struct xenhcd_info * info = xenhcd_hcd_to_info ( hcd ) ;
struct urb_priv * urbp ;
unsigned long flags ;
int ret = 0 ;
spin_lock_irqsave ( & info - > lock , flags ) ;
urbp = urb - > hcpriv ;
if ( urbp ) {
urbp - > status = status ;
ret = xenhcd_unlink_urb ( info , urbp ) ;
}
spin_unlock_irqrestore ( & info - > lock , flags ) ;
return ret ;
}
/*
* called from usb_get_current_frame_number ( ) ,
* but , almost all drivers not use such function .
*/
static int xenhcd_get_frame ( struct usb_hcd * hcd )
{
/* it means error, but probably no problem :-) */
return 0 ;
}
static struct hc_driver xenhcd_usb20_hc_driver = {
. description = " xen-hcd " ,
. product_desc = " Xen USB2.0 Virtual Host Controller " ,
. hcd_priv_size = sizeof ( struct xenhcd_info ) ,
. flags = HCD_USB2 ,
/* basic HC lifecycle operations */
. reset = xenhcd_setup ,
. start = xenhcd_run ,
. stop = xenhcd_stop ,
/* managing urb I/O */
. urb_enqueue = xenhcd_urb_enqueue ,
. urb_dequeue = xenhcd_urb_dequeue ,
. get_frame_number = xenhcd_get_frame ,
/* root hub operations */
. hub_status_data = xenhcd_hub_status_data ,
. hub_control = xenhcd_hub_control ,
# ifdef CONFIG_PM
. bus_suspend = xenhcd_bus_suspend ,
. bus_resume = xenhcd_bus_resume ,
# endif
} ;
static struct hc_driver xenhcd_usb11_hc_driver = {
. description = " xen-hcd " ,
. product_desc = " Xen USB1.1 Virtual Host Controller " ,
. hcd_priv_size = sizeof ( struct xenhcd_info ) ,
. flags = HCD_USB11 ,
/* basic HC lifecycle operations */
. reset = xenhcd_setup ,
. start = xenhcd_run ,
. stop = xenhcd_stop ,
/* managing urb I/O */
. urb_enqueue = xenhcd_urb_enqueue ,
. urb_dequeue = xenhcd_urb_dequeue ,
. get_frame_number = xenhcd_get_frame ,
/* root hub operations */
. hub_status_data = xenhcd_hub_status_data ,
. hub_control = xenhcd_hub_control ,
# ifdef CONFIG_PM
. bus_suspend = xenhcd_bus_suspend ,
. bus_resume = xenhcd_bus_resume ,
# endif
} ;
static struct usb_hcd * xenhcd_create_hcd ( struct xenbus_device * dev )
{
int i ;
int err = 0 ;
int num_ports ;
int usb_ver ;
struct usb_hcd * hcd = NULL ;
struct xenhcd_info * info ;
err = xenbus_scanf ( XBT_NIL , dev - > otherend , " num-ports " , " %d " ,
& num_ports ) ;
if ( err ! = 1 ) {
xenbus_dev_fatal ( dev , err , " reading num-ports " ) ;
return ERR_PTR ( - EINVAL ) ;
}
if ( num_ports < 1 | | num_ports > XENUSB_MAX_PORTNR ) {
xenbus_dev_fatal ( dev , err , " invalid num-ports " ) ;
return ERR_PTR ( - EINVAL ) ;
}
err = xenbus_scanf ( XBT_NIL , dev - > otherend , " usb-ver " , " %d " , & usb_ver ) ;
if ( err ! = 1 ) {
xenbus_dev_fatal ( dev , err , " reading usb-ver " ) ;
return ERR_PTR ( - EINVAL ) ;
}
switch ( usb_ver ) {
case XENUSB_VER_USB11 :
hcd = usb_create_hcd ( & xenhcd_usb11_hc_driver , & dev - > dev ,
dev_name ( & dev - > dev ) ) ;
break ;
case XENUSB_VER_USB20 :
hcd = usb_create_hcd ( & xenhcd_usb20_hc_driver , & dev - > dev ,
dev_name ( & dev - > dev ) ) ;
break ;
default :
xenbus_dev_fatal ( dev , err , " invalid usb-ver " ) ;
return ERR_PTR ( - EINVAL ) ;
}
if ( ! hcd ) {
xenbus_dev_fatal ( dev , err ,
" fail to allocate USB host controller " ) ;
return ERR_PTR ( - ENOMEM ) ;
}
info = xenhcd_hcd_to_info ( hcd ) ;
info - > xbdev = dev ;
info - > rh_numports = num_ports ;
for ( i = 0 ; i < XENUSB_URB_RING_SIZE ; i + + ) {
info - > shadow [ i ] . req . id = i + 1 ;
info - > shadow [ i ] . urb = NULL ;
2022-03-11 11:35:09 +01:00
info - > shadow [ i ] . in_flight = false ;
2021-11-23 14:20:47 +01:00
}
info - > shadow [ XENUSB_URB_RING_SIZE - 1 ] . req . id = 0x0fff ;
return hcd ;
}
static void xenhcd_backend_changed ( struct xenbus_device * dev ,
enum xenbus_state backend_state )
{
switch ( backend_state ) {
case XenbusStateInitialising :
case XenbusStateReconfiguring :
case XenbusStateReconfigured :
case XenbusStateUnknown :
break ;
case XenbusStateInitWait :
case XenbusStateInitialised :
case XenbusStateConnected :
if ( dev - > state ! = XenbusStateInitialising )
break ;
if ( ! xenhcd_connect ( dev ) )
xenbus_switch_state ( dev , XenbusStateConnected ) ;
break ;
case XenbusStateClosed :
if ( dev - > state = = XenbusStateClosed )
break ;
fallthrough ; /* Missed the backend's Closing state. */
case XenbusStateClosing :
xenhcd_disconnect ( dev ) ;
break ;
default :
xenbus_dev_fatal ( dev , - EINVAL , " saw state %d at frontend " ,
backend_state ) ;
break ;
}
}
2022-12-13 23:46:52 +08:00
static void xenhcd_remove ( struct xenbus_device * dev )
2021-11-23 14:20:47 +01:00
{
struct xenhcd_info * info = dev_get_drvdata ( & dev - > dev ) ;
struct usb_hcd * hcd = xenhcd_info_to_hcd ( info ) ;
xenhcd_destroy_rings ( info ) ;
usb_put_hcd ( hcd ) ;
}
static int xenhcd_probe ( struct xenbus_device * dev ,
const struct xenbus_device_id * id )
{
int err ;
struct usb_hcd * hcd ;
struct xenhcd_info * info ;
if ( usb_disabled ( ) )
return - ENODEV ;
hcd = xenhcd_create_hcd ( dev ) ;
if ( IS_ERR ( hcd ) ) {
err = PTR_ERR ( hcd ) ;
xenbus_dev_fatal ( dev , err ,
" fail to create usb host controller " ) ;
return err ;
}
info = xenhcd_hcd_to_info ( hcd ) ;
dev_set_drvdata ( & dev - > dev , info ) ;
err = usb_add_hcd ( hcd , 0 , 0 ) ;
if ( err ) {
xenbus_dev_fatal ( dev , err , " fail to add USB host controller " ) ;
usb_put_hcd ( hcd ) ;
dev_set_drvdata ( & dev - > dev , NULL ) ;
}
return err ;
}
static const struct xenbus_device_id xenhcd_ids [ ] = {
{ " vusb " } ,
{ " " } ,
} ;
static struct xenbus_driver xenhcd_driver = {
. ids = xenhcd_ids ,
. probe = xenhcd_probe ,
. otherend_changed = xenhcd_backend_changed ,
. remove = xenhcd_remove ,
} ;
static int __init xenhcd_init ( void )
{
if ( ! xen_domain ( ) )
return - ENODEV ;
xenhcd_urbp_cachep = kmem_cache_create ( " xenhcd_urb_priv " ,
sizeof ( struct urb_priv ) , 0 , 0 , NULL ) ;
if ( ! xenhcd_urbp_cachep ) {
pr_err ( " xenhcd failed to create kmem cache \n " ) ;
return - ENOMEM ;
}
return xenbus_register_frontend ( & xenhcd_driver ) ;
}
module_init ( xenhcd_init ) ;
static void __exit xenhcd_exit ( void )
{
kmem_cache_destroy ( xenhcd_urbp_cachep ) ;
xenbus_unregister_driver ( & xenhcd_driver ) ;
}
module_exit ( xenhcd_exit ) ;
MODULE_ALIAS ( " xen:vusb " ) ;
MODULE_AUTHOR ( " Juergen Gross <jgross@suse.com> " ) ;
MODULE_DESCRIPTION ( " Xen USB Virtual Host Controller driver (xen-hcd) " ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;