2007-02-10 12:44:02 +03:00
/*
2005-04-17 02:20:36 +04:00
* Copyright ( C ) 2001 , 2002 Jeff Dike ( jdike @ karaya . com )
* Licensed under the GPL
*/
# include "linux/list.h"
# include "linux/sched.h"
# include "linux/slab.h"
# include "linux/interrupt.h"
# include "linux/spinlock.h"
# include "linux/errno.h"
# include "asm/atomic.h"
# include "asm/semaphore.h"
# include "asm/errno.h"
# include "kern_util.h"
# include "kern.h"
# include "irq_user.h"
# include "irq_kern.h"
# include "port.h"
# include "init.h"
# include "os.h"
struct port_list {
struct list_head list ;
atomic_t wait_count ;
int has_connection ;
struct completion done ;
int port ;
int fd ;
spinlock_t lock ;
struct list_head pending ;
struct list_head connections ;
} ;
struct port_dev {
struct port_list * port ;
int helper_pid ;
int telnetd_pid ;
} ;
struct connection {
struct list_head list ;
int fd ;
int helper_pid ;
int socket [ 2 ] ;
int telnetd_pid ;
struct port_list * port ;
} ;
2006-10-09 01:49:34 +04:00
static irqreturn_t pipe_interrupt ( int irq , void * data )
2005-04-17 02:20:36 +04:00
{
struct connection * conn = data ;
int fd ;
fd = os_rcv_fd ( conn - > socket [ 0 ] , & conn - > helper_pid ) ;
if ( fd < 0 ) {
if ( fd = = - EAGAIN )
2007-02-10 12:44:02 +03:00
return IRQ_NONE ;
2005-04-17 02:20:36 +04:00
2007-02-10 12:44:02 +03:00
printk ( KERN_ERR " pipe_interrupt : os_rcv_fd returned %d \n " ,
2005-04-17 02:20:36 +04:00
- fd ) ;
os_close_file ( conn - > fd ) ;
}
list_del ( & conn - > list ) ;
conn - > fd = fd ;
list_add ( & conn - > list , & conn - > port - > connections ) ;
complete ( & conn - > port - > done ) ;
2007-02-10 12:44:02 +03:00
return IRQ_HANDLED ;
2005-04-17 02:20:36 +04:00
}
# define NO_WAITER_MSG \
" **** \n " \
" There are currently no UML consoles waiting for port connections. \n " \
" Either disconnect from one to make it available or activate some more \n " \
" by enabling more consoles in the UML /etc/inittab. \n " \
" **** \n "
static int port_accept ( struct port_list * port )
{
struct connection * conn ;
int fd , socket [ 2 ] , pid , ret = 0 ;
fd = port_connection ( port - > fd , socket , & pid ) ;
if ( fd < 0 ) {
if ( fd ! = - EAGAIN )
printk ( KERN_ERR " port_accept : port_connection "
" returned %d \n " , - fd ) ;
goto out ;
}
conn = kmalloc ( sizeof ( * conn ) , GFP_ATOMIC ) ;
if ( conn = = NULL ) {
printk ( KERN_ERR " port_accept : failed to allocate "
" connection \n " ) ;
goto out_close ;
}
2007-02-10 12:44:02 +03:00
* conn = ( ( struct connection )
2005-04-17 02:20:36 +04:00
{ . list = LIST_HEAD_INIT ( conn - > list ) ,
. fd = fd ,
. socket = { socket [ 0 ] , socket [ 1 ] } ,
. telnetd_pid = pid ,
. port = port } ) ;
2007-02-10 12:44:02 +03:00
if ( um_request_irq ( TELNETD_IRQ , socket [ 0 ] , IRQ_READ , pipe_interrupt ,
2006-07-02 06:29:27 +04:00
IRQF_DISABLED | IRQF_SHARED | IRQF_SAMPLE_RANDOM ,
2005-04-17 02:20:36 +04:00
" telnetd " , conn ) ) {
printk ( KERN_ERR " port_accept : failed to get IRQ for "
" telnetd \n " ) ;
goto out_free ;
}
if ( atomic_read ( & port - > wait_count ) = = 0 ) {
2007-05-07 01:51:43 +04:00
os_write_file ( fd , NO_WAITER_MSG , sizeof ( NO_WAITER_MSG ) ) ;
2005-04-17 02:20:36 +04:00
printk ( " No one waiting for port \n " ) ;
}
list_add ( & conn - > list , & port - > pending ) ;
2007-02-10 12:44:02 +03:00
return 1 ;
2005-04-17 02:20:36 +04:00
out_free :
kfree ( conn ) ;
out_close :
os_close_file ( fd ) ;
2007-02-10 12:44:02 +03:00
if ( pid ! = - 1 )
2005-04-17 02:20:36 +04:00
os_kill_process ( pid , 1 ) ;
out :
2007-02-10 12:44:02 +03:00
return ret ;
}
2005-04-17 02:20:36 +04:00
2007-02-10 12:44:01 +03:00
static DECLARE_MUTEX ( ports_sem ) ;
2007-02-10 12:44:04 +03:00
static LIST_HEAD ( ports ) ;
2005-04-17 02:20:36 +04:00
2006-12-05 22:36:26 +03:00
void port_work_proc ( struct work_struct * unused )
2005-04-17 02:20:36 +04:00
{
struct port_list * port ;
struct list_head * ele ;
unsigned long flags ;
local_irq_save ( flags ) ;
list_for_each ( ele , & ports ) {
port = list_entry ( ele , struct port_list , list ) ;
if ( ! port - > has_connection )
continue ;
reactivate_fd ( port - > fd , ACCEPT_IRQ ) ;
while ( port_accept ( port ) ) ;
port - > has_connection = 0 ;
}
local_irq_restore ( flags ) ;
}
2006-12-05 22:36:26 +03:00
DECLARE_WORK ( port_work , port_work_proc ) ;
2005-04-17 02:20:36 +04:00
2006-10-09 01:49:34 +04:00
static irqreturn_t port_interrupt ( int irq , void * data )
2005-04-17 02:20:36 +04:00
{
struct port_list * port = data ;
port - > has_connection = 1 ;
schedule_work ( & port_work ) ;
2007-02-10 12:44:02 +03:00
return IRQ_HANDLED ;
}
2005-04-17 02:20:36 +04:00
void * port_data ( int port_num )
{
struct list_head * ele ;
struct port_list * port ;
struct port_dev * dev = NULL ;
int fd ;
down ( & ports_sem ) ;
list_for_each ( ele , & ports ) {
port = list_entry ( ele , struct port_list , list ) ;
if ( port - > port = = port_num ) goto found ;
}
port = kmalloc ( sizeof ( struct port_list ) , GFP_KERNEL ) ;
if ( port = = NULL ) {
printk ( KERN_ERR " Allocation of port list failed \n " ) ;
goto out ;
}
fd = port_listen_fd ( port_num ) ;
if ( fd < 0 ) {
printk ( KERN_ERR " binding to port %d failed, errno = %d \n " ,
port_num , - fd ) ;
goto out_free ;
}
2007-02-10 12:44:02 +03:00
if ( um_request_irq ( ACCEPT_IRQ , fd , IRQ_READ , port_interrupt ,
IRQF_DISABLED | IRQF_SHARED | IRQF_SAMPLE_RANDOM ,
" port " , port ) ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_ERR " Failed to get IRQ for port %d \n " , port_num ) ;
goto out_close ;
}
2007-02-10 12:44:02 +03:00
* port = ( ( struct port_list )
2005-04-17 02:20:36 +04:00
{ . list = LIST_HEAD_INIT ( port - > list ) ,
. wait_count = ATOMIC_INIT ( 0 ) ,
. has_connection = 0 ,
. port = port_num ,
. fd = fd ,
. pending = LIST_HEAD_INIT ( port - > pending ) ,
. connections = LIST_HEAD_INIT ( port - > connections ) } ) ;
spin_lock_init ( & port - > lock ) ;
init_completion ( & port - > done ) ;
list_add ( & port - > list , & ports ) ;
found :
dev = kmalloc ( sizeof ( struct port_dev ) , GFP_KERNEL ) ;
if ( dev = = NULL ) {
printk ( KERN_ERR " Allocation of port device entry failed \n " ) ;
goto out ;
}
* dev = ( ( struct port_dev ) { . port = port ,
. helper_pid = - 1 ,
. telnetd_pid = - 1 } ) ;
goto out ;
out_free :
kfree ( port ) ;
out_close :
os_close_file ( fd ) ;
out :
up ( & ports_sem ) ;
2007-02-10 12:44:02 +03:00
return dev ;
2005-04-17 02:20:36 +04:00
}
int port_wait ( void * data )
{
struct port_dev * dev = data ;
struct connection * conn ;
struct port_list * port = dev - > port ;
int fd ;
2007-02-10 12:44:02 +03:00
atomic_inc ( & port - > wait_count ) ;
2005-04-17 02:20:36 +04:00
while ( 1 ) {
fd = - ERESTARTSYS ;
2007-02-10 12:44:02 +03:00
if ( wait_for_completion_interruptible ( & port - > done ) )
goto out ;
2005-04-17 02:20:36 +04:00
spin_lock ( & port - > lock ) ;
2007-02-10 12:44:02 +03:00
conn = list_entry ( port - > connections . next , struct connection ,
2005-04-17 02:20:36 +04:00
list ) ;
list_del ( & conn - > list ) ;
spin_unlock ( & port - > lock ) ;
os_shutdown_socket ( conn - > socket [ 0 ] , 1 , 1 ) ;
os_close_file ( conn - > socket [ 0 ] ) ;
os_shutdown_socket ( conn - > socket [ 1 ] , 1 , 1 ) ;
2007-02-10 12:44:02 +03:00
os_close_file ( conn - > socket [ 1 ] ) ;
2005-04-17 02:20:36 +04:00
/* This is done here because freeing an IRQ can't be done
* within the IRQ handler . So , pipe_interrupt always ups
* the semaphore regardless of whether it got a successful
2007-02-10 12:44:02 +03:00
* connection . Then we loop here throwing out failed
2005-04-17 02:20:36 +04:00
* connections until a good one is found .
*/
free_irq ( TELNETD_IRQ , conn ) ;
if ( conn - > fd > = 0 ) break ;
os_close_file ( conn - > fd ) ;
kfree ( conn ) ;
}
fd = conn - > fd ;
dev - > helper_pid = conn - > helper_pid ;
dev - > telnetd_pid = conn - > telnetd_pid ;
kfree ( conn ) ;
out :
atomic_dec ( & port - > wait_count ) ;
return fd ;
}
void port_remove_dev ( void * d )
{
struct port_dev * dev = d ;
if ( dev - > helper_pid ! = - 1 )
os_kill_process ( dev - > helper_pid , 0 ) ;
if ( dev - > telnetd_pid ! = - 1 )
os_kill_process ( dev - > telnetd_pid , 1 ) ;
dev - > helper_pid = - 1 ;
dev - > telnetd_pid = - 1 ;
}
void port_kern_free ( void * d )
{
struct port_dev * dev = d ;
port_remove_dev ( dev ) ;
kfree ( dev ) ;
}
static void free_port ( void )
{
struct list_head * ele ;
struct port_list * port ;
list_for_each ( ele , & ports ) {
port = list_entry ( ele , struct port_list , list ) ;
free_irq_by_fd ( port - > fd ) ;
os_close_file ( port - > fd ) ;
}
}
__uml_exitcall ( free_port ) ;