2008-07-09 14:56:51 -06:00
/*
* Copyright ( C ) 2003 - 2008 Takahiro Hirofuchi
2016-03-24 10:50:59 +09:00
* Copyright ( C ) 2015 Nobuo Iwata
2008-07-09 14:56:51 -06:00
*
* This is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This 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 . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 ,
* USA .
*/
2009-07-21 00:46:13 -06:00
# include <linux/kthread.h>
2011-07-10 13:09:12 -04:00
# include <linux/export.h>
2016-03-24 10:50:59 +09:00
# include <linux/slab.h>
# include <linux/workqueue.h>
2011-05-11 22:33:43 -07:00
2011-05-06 03:47:47 -07:00
# include "usbip_common.h"
2008-07-09 14:56:51 -06:00
2016-03-24 10:50:59 +09:00
struct usbip_event {
struct list_head node ;
struct usbip_device * ud ;
} ;
static DEFINE_SPINLOCK ( event_lock ) ;
static LIST_HEAD ( event_list ) ;
static void set_event ( struct usbip_device * ud , unsigned long event )
2008-07-09 14:56:51 -06:00
{
2016-03-24 10:50:59 +09:00
unsigned long flags ;
2008-07-09 14:56:51 -06:00
2016-03-24 10:50:59 +09:00
spin_lock_irqsave ( & ud - > lock , flags ) ;
ud - > event | = event ;
spin_unlock_irqrestore ( & ud - > lock , flags ) ;
}
static void unset_event ( struct usbip_device * ud , unsigned long event )
{
unsigned long flags ;
spin_lock_irqsave ( & ud - > lock , flags ) ;
ud - > event & = ~ event ;
spin_unlock_irqrestore ( & ud - > lock , flags ) ;
}
static struct usbip_device * get_event ( void )
{
struct usbip_event * ue = NULL ;
struct usbip_device * ud = NULL ;
unsigned long flags ;
spin_lock_irqsave ( & event_lock , flags ) ;
if ( ! list_empty ( & event_list ) ) {
ue = list_first_entry ( & event_list , struct usbip_event , node ) ;
list_del ( & ue - > node ) ;
}
spin_unlock_irqrestore ( & event_lock , flags ) ;
if ( ue ) {
ud = ue - > ud ;
kfree ( ue ) ;
}
return ud ;
}
static struct task_struct * worker_context ;
static void event_handler ( struct work_struct * work )
{
struct usbip_device * ud ;
if ( worker_context = = NULL ) {
worker_context = current ;
}
while ( ( ud = get_event ( ) ) ! = NULL ) {
2009-07-21 00:46:13 -06:00
usbip_dbg_eh ( " pending event %lx \n " , ud - > event ) ;
2008-07-09 14:56:51 -06:00
/*
* NOTE : shutdown must come first .
* Shutdown the device .
*/
if ( ud - > event & USBIP_EH_SHUTDOWN ) {
ud - > eh_ops . shutdown ( ud ) ;
2016-03-24 10:50:59 +09:00
unset_event ( ud , USBIP_EH_SHUTDOWN ) ;
2008-07-09 14:56:51 -06:00
}
/* Reset the device. */
if ( ud - > event & USBIP_EH_RESET ) {
ud - > eh_ops . reset ( ud ) ;
2016-03-24 10:50:59 +09:00
unset_event ( ud , USBIP_EH_RESET ) ;
2008-07-09 14:56:51 -06:00
}
/* Mark the device as unusable. */
if ( ud - > event & USBIP_EH_UNUSABLE ) {
ud - > eh_ops . unusable ( ud ) ;
2016-03-24 10:50:59 +09:00
unset_event ( ud , USBIP_EH_UNUSABLE ) ;
2008-07-09 14:56:51 -06:00
}
2010-09-21 17:43:30 +02:00
/* Stop the error handler. */
if ( ud - > event & USBIP_EH_BYE )
2016-03-24 10:50:59 +09:00
usbip_dbg_eh ( " removed %p \n " , ud ) ;
wake_up ( & ud - > eh_waitq ) ;
2008-07-09 14:56:51 -06:00
}
2016-03-24 10:50:59 +09:00
}
2008-07-09 14:56:51 -06:00
2016-03-24 10:50:59 +09:00
int usbip_start_eh ( struct usbip_device * ud )
{
init_waitqueue_head ( & ud - > eh_waitq ) ;
ud - > event = 0 ;
2008-07-09 14:56:51 -06:00
return 0 ;
}
2016-03-24 10:50:59 +09:00
EXPORT_SYMBOL_GPL ( usbip_start_eh ) ;
2008-07-09 14:56:51 -06:00
2016-03-24 10:50:59 +09:00
void usbip_stop_eh ( struct usbip_device * ud )
2008-07-09 14:56:51 -06:00
{
2016-03-24 10:50:59 +09:00
unsigned long pending = ud - > event & ~ USBIP_EH_BYE ;
2008-07-09 14:56:51 -06:00
2016-03-24 10:50:59 +09:00
if ( ! ( ud - > event & USBIP_EH_BYE ) )
usbip_dbg_eh ( " usbip_eh stopping but not removed \n " ) ;
2008-07-09 14:56:51 -06:00
2016-03-24 10:50:59 +09:00
if ( pending )
usbip_dbg_eh ( " usbip_eh waiting completion %lx \n " , pending ) ;
2011-05-06 03:47:47 -07:00
2016-03-24 10:50:59 +09:00
wait_event_interruptible ( ud - > eh_waitq , ! ( ud - > event & ~ USBIP_EH_BYE ) ) ;
usbip_dbg_eh ( " usbip_eh has stopped \n " ) ;
2008-07-09 14:56:51 -06:00
}
2016-03-24 10:50:59 +09:00
EXPORT_SYMBOL_GPL ( usbip_stop_eh ) ;
2008-07-09 14:56:51 -06:00
2016-03-24 10:50:59 +09:00
# define WORK_QUEUE_NAME "usbip_event"
2008-07-09 14:56:51 -06:00
2016-03-24 10:50:59 +09:00
static struct workqueue_struct * usbip_queue ;
static DECLARE_WORK ( usbip_work , event_handler ) ;
2011-05-06 03:47:47 -07:00
2016-03-24 10:50:59 +09:00
int usbip_init_eh ( void )
{
usbip_queue = create_singlethread_workqueue ( WORK_QUEUE_NAME ) ;
if ( usbip_queue = = NULL ) {
pr_err ( " failed to create usbip_event \n " ) ;
return - ENOMEM ;
}
2009-07-21 00:46:13 -06:00
return 0 ;
2008-07-09 14:56:51 -06:00
}
2016-03-24 10:50:59 +09:00
void usbip_finish_eh ( void )
2008-07-09 14:56:51 -06:00
{
2016-03-24 10:50:59 +09:00
flush_workqueue ( usbip_queue ) ;
destroy_workqueue ( usbip_queue ) ;
usbip_queue = NULL ;
2008-07-09 14:56:51 -06:00
}
void usbip_event_add ( struct usbip_device * ud , unsigned long event )
{
2016-03-24 10:50:59 +09:00
struct usbip_event * ue ;
2013-01-22 13:31:30 +08:00
unsigned long flags ;
2016-03-24 10:50:59 +09:00
if ( ud - > event & USBIP_EH_BYE )
return ;
set_event ( ud , event ) ;
spin_lock_irqsave ( & event_lock , flags ) ;
list_for_each_entry_reverse ( ue , & event_list , node ) {
if ( ue - > ud = = ud )
goto out ;
}
ue = kmalloc ( sizeof ( struct usbip_event ) , GFP_ATOMIC ) ;
if ( ue = = NULL )
goto out ;
ue - > ud = ud ;
list_add_tail ( & ue - > node , & event_list ) ;
queue_work ( usbip_queue , & usbip_work ) ;
out :
spin_unlock_irqrestore ( & event_lock , flags ) ;
2008-07-09 14:56:51 -06:00
}
EXPORT_SYMBOL_GPL ( usbip_event_add ) ;
2009-07-21 00:46:13 -06:00
int usbip_event_happened ( struct usbip_device * ud )
2008-07-09 14:56:51 -06:00
{
2009-07-21 00:46:13 -06:00
int happened = 0 ;
2016-02-02 17:36:39 +00:00
unsigned long flags ;
2008-07-09 14:56:51 -06:00
2016-02-02 17:36:39 +00:00
spin_lock_irqsave ( & ud - > lock , flags ) ;
2008-07-09 14:56:51 -06:00
if ( ud - > event ! = 0 )
2009-07-21 00:46:13 -06:00
happened = 1 ;
2016-02-02 17:36:39 +00:00
spin_unlock_irqrestore ( & ud - > lock , flags ) ;
2008-07-09 14:56:51 -06:00
2009-07-21 00:46:13 -06:00
return happened ;
2008-07-09 14:56:51 -06:00
}
2009-07-21 00:46:13 -06:00
EXPORT_SYMBOL_GPL ( usbip_event_happened ) ;
2016-03-24 10:50:59 +09:00
int usbip_in_eh ( struct task_struct * task )
{
if ( task = = worker_context )
return 1 ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( usbip_in_eh ) ;