2008-02-07 12:57:12 +03:00
/*
* IPWireless 3 G PCMCIA Network Driver
*
* Original code
* by Stephen Blackheath < stephen @ blacksapphire . com > ,
* Ben Martel < benm @ symmetric . co . nz >
*
* Copyrighted as follows :
* Copyright ( C ) 2004 by Symmetric Systems Ltd ( NZ )
*
* Various driver changes and rewrites , port to new kernels
* Copyright ( C ) 2006 - 2007 Jiri Kosina
*
* Misc code cleanups and updates
* Copyright ( C ) 2007 David Sterba
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/ppp_defs.h>
# include <linux/if.h>
2012-03-04 16:56:55 +04:00
# include <linux/ppp-ioctl.h>
2008-02-07 12:57:12 +03:00
# include <linux/sched.h>
# include <linux/serial.h>
# include <linux/slab.h>
# include <linux/tty.h>
# include <linux/tty_driver.h>
# include <linux/tty_flip.h>
# include <linux/uaccess.h>
# include "tty.h"
# include "network.h"
# include "hardware.h"
# include "main.h"
# define IPWIRELESS_PCMCIA_START (0)
# define IPWIRELESS_PCMCIA_MINORS (24)
# define IPWIRELESS_PCMCIA_MINOR_RANGE (8)
# define TTYTYPE_MODEM (0)
# define TTYTYPE_MONITOR (1)
# define TTYTYPE_RAS_RAW (2)
struct ipw_tty {
2012-04-02 15:54:33 +04:00
struct tty_port port ;
2008-02-07 12:57:12 +03:00
int index ;
struct ipw_hardware * hardware ;
unsigned int channel_idx ;
unsigned int secondary_channel_idx ;
int tty_type ;
struct ipw_network * network ;
unsigned int control_lines ;
struct mutex ipw_tty_mutex ;
int tx_bytes_queued ;
int closing ;
} ;
static struct ipw_tty * ttys [ IPWIRELESS_PCMCIA_MINORS ] ;
static struct tty_driver * ipw_tty_driver ;
static char * tty_type_name ( int tty_type )
{
static char * channel_names [ ] = {
" modem " ,
" monitor " ,
" RAS-raw "
} ;
return channel_names [ tty_type ] ;
}
2012-03-05 17:52:02 +04:00
static struct ipw_tty * get_tty ( int index )
2008-02-07 12:57:12 +03:00
{
2012-03-05 17:52:02 +04:00
/*
* The ' ras_raw ' channel is only available when ' loopback ' mode
* is enabled .
* Number of minor starts with 16 ( _RANGE * _RAS_RAW ) .
*/
if ( ! ipwireless_loopback & & index > =
IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW )
2008-02-07 12:57:12 +03:00
return NULL ;
2012-03-05 17:52:02 +04:00
return ttys [ index ] ;
2008-02-07 12:57:12 +03:00
}
static int ipw_open ( struct tty_struct * linux_tty , struct file * filp )
{
2012-03-05 17:52:02 +04:00
struct ipw_tty * tty = get_tty ( linux_tty - > index ) ;
2008-02-07 12:57:12 +03:00
if ( ! tty )
return - ENODEV ;
mutex_lock ( & tty - > ipw_tty_mutex ) ;
2012-04-02 15:54:33 +04:00
if ( tty - > port . count = = 0 )
2008-02-07 12:57:12 +03:00
tty - > tx_bytes_queued = 0 ;
2012-04-02 15:54:33 +04:00
tty - > port . count + + ;
2008-02-07 12:57:12 +03:00
2012-04-02 15:54:34 +04:00
tty - > port . tty = linux_tty ;
2008-02-07 12:57:12 +03:00
linux_tty - > driver_data = tty ;
2013-01-03 18:53:05 +04:00
tty - > port . low_latency = 1 ;
2008-02-07 12:57:12 +03:00
if ( tty - > tty_type = = TTYTYPE_MODEM )
ipwireless_ppp_open ( tty - > network ) ;
mutex_unlock ( & tty - > ipw_tty_mutex ) ;
return 0 ;
}
static void do_ipw_close ( struct ipw_tty * tty )
{
2012-04-02 15:54:33 +04:00
tty - > port . count - - ;
2008-02-07 12:57:12 +03:00
2012-04-02 15:54:33 +04:00
if ( tty - > port . count = = 0 ) {
2012-04-02 15:54:34 +04:00
struct tty_struct * linux_tty = tty - > port . tty ;
2008-02-07 12:57:12 +03:00
if ( linux_tty ! = NULL ) {
2012-04-02 15:54:34 +04:00
tty - > port . tty = NULL ;
2008-02-07 12:57:12 +03:00
linux_tty - > driver_data = NULL ;
if ( tty - > tty_type = = TTYTYPE_MODEM )
ipwireless_ppp_close ( tty - > network ) ;
}
}
}
static void ipw_hangup ( struct tty_struct * linux_tty )
{
struct ipw_tty * tty = linux_tty - > driver_data ;
if ( ! tty )
return ;
mutex_lock ( & tty - > ipw_tty_mutex ) ;
2012-04-02 15:54:33 +04:00
if ( tty - > port . count = = 0 ) {
2008-02-07 12:57:12 +03:00
mutex_unlock ( & tty - > ipw_tty_mutex ) ;
return ;
}
do_ipw_close ( tty ) ;
mutex_unlock ( & tty - > ipw_tty_mutex ) ;
}
static void ipw_close ( struct tty_struct * linux_tty , struct file * filp )
{
ipw_hangup ( linux_tty ) ;
}
/* Take data received from hardware, and send it out the tty */
void ipwireless_tty_received ( struct ipw_tty * tty , unsigned char * data ,
unsigned int length )
{
int work = 0 ;
mutex_lock ( & tty - > ipw_tty_mutex ) ;
2012-04-02 15:54:33 +04:00
if ( ! tty - > port . count ) {
2008-02-07 12:57:12 +03:00
mutex_unlock ( & tty - > ipw_tty_mutex ) ;
return ;
}
mutex_unlock ( & tty - > ipw_tty_mutex ) ;
2013-01-03 18:53:04 +04:00
work = tty_insert_flip_string ( & tty - > port , data , length ) ;
2008-02-07 12:57:12 +03:00
if ( work ! = length )
printk ( KERN_DEBUG IPWIRELESS_PCCARD_NAME
" : %d chars not inserted to flip buffer! \n " ,
length - work ) ;
if ( work )
2013-01-03 18:53:06 +04:00
tty_flip_buffer_push ( & tty - > port ) ;
2008-02-07 12:57:12 +03:00
}
static void ipw_write_packet_sent_callback ( void * callback_data ,
unsigned int packet_length )
{
struct ipw_tty * tty = callback_data ;
/*
* Packet has been sent , so we subtract the number of bytes from our
* tally of outstanding TX bytes .
*/
tty - > tx_bytes_queued - = packet_length ;
}
static int ipw_write ( struct tty_struct * linux_tty ,
const unsigned char * buf , int count )
{
struct ipw_tty * tty = linux_tty - > driver_data ;
int room , ret ;
if ( ! tty )
return - ENODEV ;
mutex_lock ( & tty - > ipw_tty_mutex ) ;
2012-04-02 15:54:33 +04:00
if ( ! tty - > port . count ) {
2008-02-07 12:57:12 +03:00
mutex_unlock ( & tty - > ipw_tty_mutex ) ;
return - EINVAL ;
}
room = IPWIRELESS_TX_QUEUE_SIZE - tty - > tx_bytes_queued ;
if ( room < 0 )
room = 0 ;
/* Don't allow caller to write any more than we have room for */
if ( count > room )
count = room ;
if ( count = = 0 ) {
mutex_unlock ( & tty - > ipw_tty_mutex ) ;
return 0 ;
}
ret = ipwireless_send_packet ( tty - > hardware , IPW_CHANNEL_RAS ,
2008-07-28 18:53:11 +04:00
buf , count ,
2008-02-07 12:57:12 +03:00
ipw_write_packet_sent_callback , tty ) ;
if ( ret = = - 1 ) {
mutex_unlock ( & tty - > ipw_tty_mutex ) ;
return 0 ;
}
tty - > tx_bytes_queued + = count ;
mutex_unlock ( & tty - > ipw_tty_mutex ) ;
return count ;
}
static int ipw_write_room ( struct tty_struct * linux_tty )
{
struct ipw_tty * tty = linux_tty - > driver_data ;
int room ;
2008-10-13 13:38:07 +04:00
/* FIXME: Exactly how is the tty object locked here .. */
2008-02-07 12:57:12 +03:00
if ( ! tty )
return - ENODEV ;
2012-04-02 15:54:33 +04:00
if ( ! tty - > port . count )
2008-02-07 12:57:12 +03:00
return - EINVAL ;
room = IPWIRELESS_TX_QUEUE_SIZE - tty - > tx_bytes_queued ;
if ( room < 0 )
room = 0 ;
return room ;
}
static int ipwireless_get_serial_info ( struct ipw_tty * tty ,
struct serial_struct __user * retinfo )
{
struct serial_struct tmp ;
memset ( & tmp , 0 , sizeof ( tmp ) ) ;
tmp . type = PORT_UNKNOWN ;
tmp . line = tty - > index ;
tmp . baud_base = 115200 ;
2016-05-09 10:11:53 +03:00
2008-02-07 12:57:12 +03:00
if ( copy_to_user ( retinfo , & tmp , sizeof ( * retinfo ) ) )
return - EFAULT ;
return 0 ;
}
static int ipw_chars_in_buffer ( struct tty_struct * linux_tty )
{
struct ipw_tty * tty = linux_tty - > driver_data ;
if ( ! tty )
2009-07-20 19:05:27 +04:00
return 0 ;
2008-02-07 12:57:12 +03:00
2012-04-02 15:54:33 +04:00
if ( ! tty - > port . count )
2009-07-20 19:05:27 +04:00
return 0 ;
2008-02-07 12:57:12 +03:00
return tty - > tx_bytes_queued ;
}
static int get_control_lines ( struct ipw_tty * tty )
{
unsigned int my = tty - > control_lines ;
unsigned int out = 0 ;
if ( my & IPW_CONTROL_LINE_RTS )
out | = TIOCM_RTS ;
if ( my & IPW_CONTROL_LINE_DTR )
out | = TIOCM_DTR ;
if ( my & IPW_CONTROL_LINE_CTS )
out | = TIOCM_CTS ;
if ( my & IPW_CONTROL_LINE_DSR )
out | = TIOCM_DSR ;
if ( my & IPW_CONTROL_LINE_DCD )
out | = TIOCM_CD ;
return out ;
}
static int set_control_lines ( struct ipw_tty * tty , unsigned int set ,
unsigned int clear )
{
int ret ;
if ( set & TIOCM_RTS ) {
ret = ipwireless_set_RTS ( tty - > hardware , tty - > channel_idx , 1 ) ;
if ( ret )
return ret ;
if ( tty - > secondary_channel_idx ! = - 1 ) {
ret = ipwireless_set_RTS ( tty - > hardware ,
tty - > secondary_channel_idx , 1 ) ;
if ( ret )
return ret ;
}
}
if ( set & TIOCM_DTR ) {
ret = ipwireless_set_DTR ( tty - > hardware , tty - > channel_idx , 1 ) ;
if ( ret )
return ret ;
if ( tty - > secondary_channel_idx ! = - 1 ) {
ret = ipwireless_set_DTR ( tty - > hardware ,
tty - > secondary_channel_idx , 1 ) ;
if ( ret )
return ret ;
}
}
if ( clear & TIOCM_RTS ) {
ret = ipwireless_set_RTS ( tty - > hardware , tty - > channel_idx , 0 ) ;
if ( tty - > secondary_channel_idx ! = - 1 ) {
ret = ipwireless_set_RTS ( tty - > hardware ,
tty - > secondary_channel_idx , 0 ) ;
if ( ret )
return ret ;
}
}
if ( clear & TIOCM_DTR ) {
ret = ipwireless_set_DTR ( tty - > hardware , tty - > channel_idx , 0 ) ;
if ( tty - > secondary_channel_idx ! = - 1 ) {
ret = ipwireless_set_DTR ( tty - > hardware ,
tty - > secondary_channel_idx , 0 ) ;
if ( ret )
return ret ;
}
}
return 0 ;
}
2011-02-14 19:26:14 +03:00
static int ipw_tiocmget ( struct tty_struct * linux_tty )
2008-02-07 12:57:12 +03:00
{
struct ipw_tty * tty = linux_tty - > driver_data ;
2008-10-13 13:38:07 +04:00
/* FIXME: Exactly how is the tty object locked here .. */
2008-02-07 12:57:12 +03:00
if ( ! tty )
return - ENODEV ;
2012-04-02 15:54:33 +04:00
if ( ! tty - > port . count )
2008-02-07 12:57:12 +03:00
return - EINVAL ;
return get_control_lines ( tty ) ;
}
static int
2011-02-14 19:26:50 +03:00
ipw_tiocmset ( struct tty_struct * linux_tty ,
2008-02-07 12:57:12 +03:00
unsigned int set , unsigned int clear )
{
struct ipw_tty * tty = linux_tty - > driver_data ;
2008-10-13 13:38:07 +04:00
/* FIXME: Exactly how is the tty object locked here .. */
2008-02-07 12:57:12 +03:00
if ( ! tty )
return - ENODEV ;
2012-04-02 15:54:33 +04:00
if ( ! tty - > port . count )
2008-02-07 12:57:12 +03:00
return - EINVAL ;
return set_control_lines ( tty , set , clear ) ;
}
2011-02-14 19:27:22 +03:00
static int ipw_ioctl ( struct tty_struct * linux_tty ,
2008-02-07 12:57:12 +03:00
unsigned int cmd , unsigned long arg )
{
struct ipw_tty * tty = linux_tty - > driver_data ;
if ( ! tty )
return - ENODEV ;
2012-04-02 15:54:33 +04:00
if ( ! tty - > port . count )
2008-02-07 12:57:12 +03:00
return - EINVAL ;
2008-10-13 13:38:07 +04:00
/* FIXME: Exactly how is the tty object locked here .. */
2008-02-07 12:57:12 +03:00
switch ( cmd ) {
case TIOCGSERIAL :
return ipwireless_get_serial_info ( tty , ( void __user * ) arg ) ;
case TIOCSSERIAL :
return 0 ; /* Keeps the PCMCIA scripts happy. */
}
if ( tty - > tty_type = = TTYTYPE_MODEM ) {
switch ( cmd ) {
case PPPIOCGCHAN :
{
int chan = ipwireless_ppp_channel_index (
tty - > network ) ;
if ( chan < 0 )
return - ENODEV ;
if ( put_user ( chan , ( int __user * ) arg ) )
return - EFAULT ;
}
return 0 ;
case PPPIOCGUNIT :
{
int unit = ipwireless_ppp_unit_number (
tty - > network ) ;
if ( unit < 0 )
return - ENODEV ;
if ( put_user ( unit , ( int __user * ) arg ) )
return - EFAULT ;
}
return 0 ;
case FIONREAD :
{
int val = 0 ;
if ( put_user ( val , ( int __user * ) arg ) )
return - EFAULT ;
}
return 0 ;
2008-10-13 13:38:07 +04:00
case TCFLSH :
return tty_perform_flush ( linux_tty , arg ) ;
2008-02-07 12:57:12 +03:00
}
}
2011-02-14 19:27:22 +03:00
return - ENOIOCTLCMD ;
2008-02-07 12:57:12 +03:00
}
2010-03-20 21:43:26 +03:00
static int add_tty ( int j ,
2008-02-07 12:57:12 +03:00
struct ipw_hardware * hardware ,
struct ipw_network * network , int channel_idx ,
int secondary_channel_idx , int tty_type )
{
ttys [ j ] = kzalloc ( sizeof ( struct ipw_tty ) , GFP_KERNEL ) ;
if ( ! ttys [ j ] )
return - ENOMEM ;
ttys [ j ] - > index = j ;
ttys [ j ] - > hardware = hardware ;
ttys [ j ] - > channel_idx = channel_idx ;
ttys [ j ] - > secondary_channel_idx = secondary_channel_idx ;
ttys [ j ] - > network = network ;
ttys [ j ] - > tty_type = tty_type ;
mutex_init ( & ttys [ j ] - > ipw_tty_mutex ) ;
2012-04-02 15:54:33 +04:00
tty_port_init ( & ttys [ j ] - > port ) ;
2008-02-07 12:57:12 +03:00
2012-08-07 23:47:47 +04:00
tty_port_register_device ( & ttys [ j ] - > port , ipw_tty_driver , j , NULL ) ;
2008-02-07 12:57:12 +03:00
ipwireless_associate_network_tty ( network , channel_idx , ttys [ j ] ) ;
if ( secondary_channel_idx ! = - 1 )
ipwireless_associate_network_tty ( network ,
secondary_channel_idx ,
ttys [ j ] ) ;
2012-04-02 15:54:32 +04:00
/* check if we provide raw device (if loopback is enabled) */
if ( get_tty ( j ) )
printk ( KERN_INFO IPWIRELESS_PCCARD_NAME
" : registering %s device ttyIPWp%d \n " ,
tty_type_name ( tty_type ) , j ) ;
2008-02-07 12:57:12 +03:00
return 0 ;
}
struct ipw_tty * ipwireless_tty_create ( struct ipw_hardware * hardware ,
2010-03-20 21:43:26 +03:00
struct ipw_network * network )
2008-02-07 12:57:12 +03:00
{
int i , j ;
for ( i = 0 ; i < IPWIRELESS_PCMCIA_MINOR_RANGE ; i + + ) {
int allfree = 1 ;
for ( j = i ; j < IPWIRELESS_PCMCIA_MINORS ;
j + = IPWIRELESS_PCMCIA_MINOR_RANGE )
if ( ttys [ j ] ! = NULL ) {
allfree = 0 ;
break ;
}
if ( allfree ) {
j = i ;
2010-03-20 21:43:26 +03:00
if ( add_tty ( j , hardware , network ,
2008-02-07 12:57:12 +03:00
IPW_CHANNEL_DIALLER , IPW_CHANNEL_RAS ,
TTYTYPE_MODEM ) )
return NULL ;
j + = IPWIRELESS_PCMCIA_MINOR_RANGE ;
2010-03-20 21:43:26 +03:00
if ( add_tty ( j , hardware , network ,
2008-02-07 12:57:12 +03:00
IPW_CHANNEL_DIALLER , - 1 ,
TTYTYPE_MONITOR ) )
return NULL ;
j + = IPWIRELESS_PCMCIA_MINOR_RANGE ;
2010-03-20 21:43:26 +03:00
if ( add_tty ( j , hardware , network ,
2008-02-07 12:57:12 +03:00
IPW_CHANNEL_RAS , - 1 ,
TTYTYPE_RAS_RAW ) )
return NULL ;
return ttys [ i ] ;
}
}
return NULL ;
}
/*
* Must be called before ipwireless_network_free ( ) .
*/
void ipwireless_tty_free ( struct ipw_tty * tty )
{
int j ;
struct ipw_network * network = ttys [ tty - > index ] - > network ;
for ( j = tty - > index ; j < IPWIRELESS_PCMCIA_MINORS ;
j + = IPWIRELESS_PCMCIA_MINOR_RANGE ) {
struct ipw_tty * ttyj = ttys [ j ] ;
if ( ttyj ) {
mutex_lock ( & ttyj - > ipw_tty_mutex ) ;
2012-04-02 15:54:32 +04:00
if ( get_tty ( j ) )
printk ( KERN_INFO IPWIRELESS_PCCARD_NAME
" : deregistering %s device ttyIPWp%d \n " ,
tty_type_name ( ttyj - > tty_type ) , j ) ;
2008-02-07 12:57:12 +03:00
ttyj - > closing = 1 ;
2012-04-02 15:54:34 +04:00
if ( ttyj - > port . tty ! = NULL ) {
2008-02-07 12:57:12 +03:00
mutex_unlock ( & ttyj - > ipw_tty_mutex ) ;
2012-04-02 15:54:34 +04:00
tty_vhangup ( ttyj - > port . tty ) ;
2008-10-13 13:38:07 +04:00
/* FIXME: Exactly how is the tty object locked here
against a parallel ioctl etc */
2012-04-02 15:54:31 +04:00
/* FIXME2: hangup does not mean all processes
* are gone */
2008-02-07 12:57:12 +03:00
mutex_lock ( & ttyj - > ipw_tty_mutex ) ;
}
2012-04-02 15:54:33 +04:00
while ( ttyj - > port . count )
2008-02-07 12:57:12 +03:00
do_ipw_close ( ttyj ) ;
ipwireless_disassociate_network_ttys ( network ,
ttyj - > channel_idx ) ;
tty_unregister_device ( ipw_tty_driver , j ) ;
2012-11-15 12:49:56 +04:00
tty_port_destroy ( & ttyj - > port ) ;
2008-02-07 12:57:12 +03:00
ttys [ j ] = NULL ;
mutex_unlock ( & ttyj - > ipw_tty_mutex ) ;
kfree ( ttyj ) ;
}
}
}
2009-10-03 00:12:06 +04:00
static const struct tty_operations tty_ops = {
2008-02-07 12:57:12 +03:00
. open = ipw_open ,
. close = ipw_close ,
. hangup = ipw_hangup ,
. write = ipw_write ,
. write_room = ipw_write_room ,
. ioctl = ipw_ioctl ,
. chars_in_buffer = ipw_chars_in_buffer ,
. tiocmget = ipw_tiocmget ,
. tiocmset = ipw_tiocmset ,
} ;
int ipwireless_tty_init ( void )
{
int result ;
ipw_tty_driver = alloc_tty_driver ( IPWIRELESS_PCMCIA_MINORS ) ;
if ( ! ipw_tty_driver )
return - ENOMEM ;
ipw_tty_driver - > driver_name = IPWIRELESS_PCCARD_NAME ;
ipw_tty_driver - > name = " ttyIPWp " ;
ipw_tty_driver - > major = 0 ;
ipw_tty_driver - > minor_start = IPWIRELESS_PCMCIA_START ;
ipw_tty_driver - > type = TTY_DRIVER_TYPE_SERIAL ;
ipw_tty_driver - > subtype = SERIAL_TYPE_NORMAL ;
ipw_tty_driver - > flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV ;
ipw_tty_driver - > init_termios = tty_std_termios ;
ipw_tty_driver - > init_termios . c_cflag =
B9600 | CS8 | CREAD | HUPCL | CLOCAL ;
ipw_tty_driver - > init_termios . c_ispeed = 9600 ;
ipw_tty_driver - > init_termios . c_ospeed = 9600 ;
tty_set_operations ( ipw_tty_driver , & tty_ops ) ;
result = tty_register_driver ( ipw_tty_driver ) ;
if ( result ) {
printk ( KERN_ERR IPWIRELESS_PCCARD_NAME
" : failed to register tty driver \n " ) ;
put_tty_driver ( ipw_tty_driver ) ;
return result ;
}
return 0 ;
}
void ipwireless_tty_release ( void )
{
int ret ;
ret = tty_unregister_driver ( ipw_tty_driver ) ;
put_tty_driver ( ipw_tty_driver ) ;
if ( ret ! = 0 )
printk ( KERN_ERR IPWIRELESS_PCCARD_NAME
" : tty_unregister_driver failed with code %d \n " , ret ) ;
}
int ipwireless_tty_is_modem ( struct ipw_tty * tty )
{
return tty - > tty_type = = TTYTYPE_MODEM ;
}
void
ipwireless_tty_notify_control_line_change ( struct ipw_tty * tty ,
unsigned int channel_idx ,
unsigned int control_lines ,
unsigned int changed_mask )
{
unsigned int old_control_lines = tty - > control_lines ;
tty - > control_lines = ( tty - > control_lines & ~ changed_mask )
| ( control_lines & changed_mask ) ;
/*
* If DCD is de - asserted , we close the tty so pppd can tell that we
* have gone offline .
*/
if ( ( old_control_lines & IPW_CONTROL_LINE_DCD )
& & ! ( tty - > control_lines & IPW_CONTROL_LINE_DCD )
2012-04-02 15:54:34 +04:00
& & tty - > port . tty ) {
tty_hangup ( tty - > port . tty ) ;
2008-02-07 12:57:12 +03:00
}
}