2005-04-16 15:20:36 -07:00
/*
* linux / drivers / char / tty_ioctl . c
*
* Copyright ( C ) 1991 , 1992 , 1993 , 1994 Linus Torvalds
*
* Modified by Fred N . van Kempen , 01 / 29 / 93 , to add line disciplines
* which can be dynamically activated and de - activated by the line
* discipline handling modules ( like SLIP ) .
*/
# include <linux/types.h>
# include <linux/termios.h>
# include <linux/errno.h>
# include <linux/sched.h>
# include <linux/kernel.h>
# include <linux/major.h>
# include <linux/tty.h>
# include <linux/fcntl.h>
# include <linux/string.h>
# include <linux/mm.h>
# include <linux/module.h>
# include <linux/bitops.h>
# include <asm/io.h>
# include <asm/uaccess.h>
# include <asm/system.h>
# undef TTY_DEBUG_WAIT_UNTIL_SENT
# undef DEBUG
/*
* Internal flag options for termios setting behavior
*/
# define TERMIOS_FLUSH 1
# define TERMIOS_WAIT 2
# define TERMIOS_TERMIO 4
void tty_wait_until_sent ( struct tty_struct * tty , long timeout )
{
DECLARE_WAITQUEUE ( wait , current ) ;
# ifdef TTY_DEBUG_WAIT_UNTIL_SENT
char buf [ 64 ] ;
printk ( KERN_DEBUG " %s wait until sent... \n " , tty_name ( tty , buf ) ) ;
# endif
if ( ! tty - > driver - > chars_in_buffer )
return ;
add_wait_queue ( & tty - > write_wait , & wait ) ;
if ( ! timeout )
timeout = MAX_SCHEDULE_TIMEOUT ;
do {
# ifdef TTY_DEBUG_WAIT_UNTIL_SENT
printk ( KERN_DEBUG " waiting %s...(%d) \n " , tty_name ( tty , buf ) ,
tty - > driver - > chars_in_buffer ( tty ) ) ;
# endif
set_current_state ( TASK_INTERRUPTIBLE ) ;
if ( signal_pending ( current ) )
goto stop_waiting ;
if ( ! tty - > driver - > chars_in_buffer ( tty ) )
break ;
timeout = schedule_timeout ( timeout ) ;
} while ( timeout ) ;
if ( tty - > driver - > wait_until_sent )
tty - > driver - > wait_until_sent ( tty , timeout ) ;
stop_waiting :
set_current_state ( TASK_RUNNING ) ;
remove_wait_queue ( & tty - > write_wait , & wait ) ;
}
EXPORT_SYMBOL ( tty_wait_until_sent ) ;
static void unset_locked_termios ( struct termios * termios ,
struct termios * old ,
struct termios * locked )
{
int i ;
# define NOSET_MASK(x,y,z) (x = ((x) & ~(z)) | ((y) & (z)))
if ( ! locked ) {
printk ( KERN_WARNING " Warning?!? termios_locked is NULL. \n " ) ;
return ;
}
NOSET_MASK ( termios - > c_iflag , old - > c_iflag , locked - > c_iflag ) ;
NOSET_MASK ( termios - > c_oflag , old - > c_oflag , locked - > c_oflag ) ;
NOSET_MASK ( termios - > c_cflag , old - > c_cflag , locked - > c_cflag ) ;
NOSET_MASK ( termios - > c_lflag , old - > c_lflag , locked - > c_lflag ) ;
termios - > c_line = locked - > c_line ? old - > c_line : termios - > c_line ;
for ( i = 0 ; i < NCCS ; i + + )
termios - > c_cc [ i ] = locked - > c_cc [ i ] ?
old - > c_cc [ i ] : termios - > c_cc [ i ] ;
}
static void change_termios ( struct tty_struct * tty , struct termios * new_termios )
{
int canon_change ;
struct termios old_termios = * tty - > termios ;
struct tty_ldisc * ld ;
/*
* Perform the actual termios internal changes under lock .
*/
/* FIXME: we need to decide on some locking/ordering semantics
for the set_termios notification eventually */
down ( & tty - > termios_sem ) ;
* tty - > termios = * new_termios ;
unset_locked_termios ( tty - > termios , & old_termios , tty - > termios_locked ) ;
canon_change = ( old_termios . c_lflag ^ tty - > termios - > c_lflag ) & ICANON ;
if ( canon_change ) {
memset ( & tty - > read_flags , 0 , sizeof tty - > read_flags ) ;
tty - > canon_head = tty - > read_tail ;
tty - > canon_data = 0 ;
tty - > erasing = 0 ;
}
if ( canon_change & & ! L_ICANON ( tty ) & & tty - > read_cnt )
/* Get characters left over from canonical mode. */
wake_up_interruptible ( & tty - > read_wait ) ;
/* See if packet mode change of state. */
if ( tty - > link & & tty - > link - > packet ) {
int old_flow = ( ( old_termios . c_iflag & IXON ) & &
( old_termios . c_cc [ VSTOP ] = = ' \023 ' ) & &
( old_termios . c_cc [ VSTART ] = = ' \021 ' ) ) ;
int new_flow = ( I_IXON ( tty ) & &
STOP_CHAR ( tty ) = = ' \023 ' & &
START_CHAR ( tty ) = = ' \021 ' ) ;
if ( old_flow ! = new_flow ) {
tty - > ctrl_status & = ~ ( TIOCPKT_DOSTOP | TIOCPKT_NOSTOP ) ;
if ( new_flow )
tty - > ctrl_status | = TIOCPKT_DOSTOP ;
else
tty - > ctrl_status | = TIOCPKT_NOSTOP ;
wake_up_interruptible ( & tty - > link - > read_wait ) ;
}
}
if ( tty - > driver - > set_termios )
( * tty - > driver - > set_termios ) ( tty , & old_termios ) ;
ld = tty_ldisc_ref ( tty ) ;
if ( ld ! = NULL ) {
if ( ld - > set_termios )
( ld - > set_termios ) ( tty , & old_termios ) ;
tty_ldisc_deref ( ld ) ;
}
up ( & tty - > termios_sem ) ;
}
static int set_termios ( struct tty_struct * tty , void __user * arg , int opt )
{
struct termios tmp_termios ;
struct tty_ldisc * ld ;
int retval = tty_check_change ( tty ) ;
if ( retval )
return retval ;
if ( opt & TERMIOS_TERMIO ) {
memcpy ( & tmp_termios , tty - > termios , sizeof ( struct termios ) ) ;
if ( user_termio_to_kernel_termios ( & tmp_termios ,
( struct termio __user * ) arg ) )
return - EFAULT ;
} else {
if ( user_termios_to_kernel_termios ( & tmp_termios ,
( struct termios __user * ) arg ) )
return - EFAULT ;
}
ld = tty_ldisc_ref ( tty ) ;
if ( ld ! = NULL ) {
if ( ( opt & TERMIOS_FLUSH ) & & ld - > flush_buffer )
ld - > flush_buffer ( tty ) ;
tty_ldisc_deref ( ld ) ;
}
if ( opt & TERMIOS_WAIT ) {
tty_wait_until_sent ( tty , 0 ) ;
if ( signal_pending ( current ) )
return - EINTR ;
}
change_termios ( tty , & tmp_termios ) ;
return 0 ;
}
static int get_termio ( struct tty_struct * tty , struct termio __user * termio )
{
if ( kernel_termios_to_user_termio ( termio , tty - > termios ) )
return - EFAULT ;
return 0 ;
}
static unsigned long inq_canon ( struct tty_struct * tty )
{
int nr , head , tail ;
if ( ! tty - > canon_data | | ! tty - > read_buf )
return 0 ;
head = tty - > canon_head ;
tail = tty - > read_tail ;
nr = ( head - tail ) & ( N_TTY_BUF_SIZE - 1 ) ;
/* Skip EOF-chars.. */
while ( head ! = tail ) {
if ( test_bit ( tail , tty - > read_flags ) & &
tty - > read_buf [ tail ] = = __DISABLED_CHAR )
nr - - ;
tail = ( tail + 1 ) & ( N_TTY_BUF_SIZE - 1 ) ;
}
return nr ;
}
# ifdef TIOCGETP
/*
* These are deprecated , but there is limited support . .
*
* The " sg_flags " translation is a joke . .
*/
static int get_sgflags ( struct tty_struct * tty )
{
int flags = 0 ;
if ( ! ( tty - > termios - > c_lflag & ICANON ) ) {
if ( tty - > termios - > c_lflag & ISIG )
flags | = 0x02 ; /* cbreak */
else
flags | = 0x20 ; /* raw */
}
if ( tty - > termios - > c_lflag & ECHO )
flags | = 0x08 ; /* echo */
if ( tty - > termios - > c_oflag & OPOST )
if ( tty - > termios - > c_oflag & ONLCR )
flags | = 0x10 ; /* crmod */
return flags ;
}
static int get_sgttyb ( struct tty_struct * tty , struct sgttyb __user * sgttyb )
{
struct sgttyb tmp ;
down ( & tty - > termios_sem ) ;
tmp . sg_ispeed = 0 ;
tmp . sg_ospeed = 0 ;
tmp . sg_erase = tty - > termios - > c_cc [ VERASE ] ;
tmp . sg_kill = tty - > termios - > c_cc [ VKILL ] ;
tmp . sg_flags = get_sgflags ( tty ) ;
up ( & tty - > termios_sem ) ;
return copy_to_user ( sgttyb , & tmp , sizeof ( tmp ) ) ? - EFAULT : 0 ;
}
static void set_sgflags ( struct termios * termios , int flags )
{
termios - > c_iflag = ICRNL | IXON ;
termios - > c_oflag = 0 ;
termios - > c_lflag = ISIG | ICANON ;
if ( flags & 0x02 ) { /* cbreak */
termios - > c_iflag = 0 ;
termios - > c_lflag & = ~ ICANON ;
}
if ( flags & 0x08 ) { /* echo */
termios - > c_lflag | = ECHO | ECHOE | ECHOK |
ECHOCTL | ECHOKE | IEXTEN ;
}
if ( flags & 0x10 ) { /* crmod */
termios - > c_oflag | = OPOST | ONLCR ;
}
if ( flags & 0x20 ) { /* raw */
termios - > c_iflag = 0 ;
termios - > c_lflag & = ~ ( ISIG | ICANON ) ;
}
if ( ! ( termios - > c_lflag & ICANON ) ) {
termios - > c_cc [ VMIN ] = 1 ;
termios - > c_cc [ VTIME ] = 0 ;
}
}
static int set_sgttyb ( struct tty_struct * tty , struct sgttyb __user * sgttyb )
{
int retval ;
struct sgttyb tmp ;
struct termios termios ;
retval = tty_check_change ( tty ) ;
if ( retval )
return retval ;
if ( copy_from_user ( & tmp , sgttyb , sizeof ( tmp ) ) )
return - EFAULT ;
down ( & tty - > termios_sem ) ;
termios = * tty - > termios ;
termios . c_cc [ VERASE ] = tmp . sg_erase ;
termios . c_cc [ VKILL ] = tmp . sg_kill ;
set_sgflags ( & termios , tmp . sg_flags ) ;
up ( & tty - > termios_sem ) ;
change_termios ( tty , & termios ) ;
return 0 ;
}
# endif
# ifdef TIOCGETC
static int get_tchars ( struct tty_struct * tty , struct tchars __user * tchars )
{
struct tchars tmp ;
tmp . t_intrc = tty - > termios - > c_cc [ VINTR ] ;
tmp . t_quitc = tty - > termios - > c_cc [ VQUIT ] ;
tmp . t_startc = tty - > termios - > c_cc [ VSTART ] ;
tmp . t_stopc = tty - > termios - > c_cc [ VSTOP ] ;
tmp . t_eofc = tty - > termios - > c_cc [ VEOF ] ;
tmp . t_brkc = tty - > termios - > c_cc [ VEOL2 ] ; /* what is brkc anyway? */
return copy_to_user ( tchars , & tmp , sizeof ( tmp ) ) ? - EFAULT : 0 ;
}
static int set_tchars ( struct tty_struct * tty , struct tchars __user * tchars )
{
struct tchars tmp ;
if ( copy_from_user ( & tmp , tchars , sizeof ( tmp ) ) )
return - EFAULT ;
tty - > termios - > c_cc [ VINTR ] = tmp . t_intrc ;
tty - > termios - > c_cc [ VQUIT ] = tmp . t_quitc ;
tty - > termios - > c_cc [ VSTART ] = tmp . t_startc ;
tty - > termios - > c_cc [ VSTOP ] = tmp . t_stopc ;
tty - > termios - > c_cc [ VEOF ] = tmp . t_eofc ;
tty - > termios - > c_cc [ VEOL2 ] = tmp . t_brkc ; /* what is brkc anyway? */
return 0 ;
}
# endif
# ifdef TIOCGLTC
static int get_ltchars ( struct tty_struct * tty , struct ltchars __user * ltchars )
{
struct ltchars tmp ;
tmp . t_suspc = tty - > termios - > c_cc [ VSUSP ] ;
tmp . t_dsuspc = tty - > termios - > c_cc [ VSUSP ] ; /* what is dsuspc anyway? */
tmp . t_rprntc = tty - > termios - > c_cc [ VREPRINT ] ;
tmp . t_flushc = tty - > termios - > c_cc [ VEOL2 ] ; /* what is flushc anyway? */
tmp . t_werasc = tty - > termios - > c_cc [ VWERASE ] ;
tmp . t_lnextc = tty - > termios - > c_cc [ VLNEXT ] ;
return copy_to_user ( ltchars , & tmp , sizeof ( tmp ) ) ? - EFAULT : 0 ;
}
static int set_ltchars ( struct tty_struct * tty , struct ltchars __user * ltchars )
{
struct ltchars tmp ;
if ( copy_from_user ( & tmp , ltchars , sizeof ( tmp ) ) )
return - EFAULT ;
tty - > termios - > c_cc [ VSUSP ] = tmp . t_suspc ;
tty - > termios - > c_cc [ VEOL2 ] = tmp . t_dsuspc ; /* what is dsuspc anyway? */
tty - > termios - > c_cc [ VREPRINT ] = tmp . t_rprntc ;
tty - > termios - > c_cc [ VEOL2 ] = tmp . t_flushc ; /* what is flushc anyway? */
tty - > termios - > c_cc [ VWERASE ] = tmp . t_werasc ;
tty - > termios - > c_cc [ VLNEXT ] = tmp . t_lnextc ;
return 0 ;
}
# endif
/*
* Send a high priority character to the tty .
*/
static void send_prio_char ( struct tty_struct * tty , char ch )
{
int was_stopped = tty - > stopped ;
if ( tty - > driver - > send_xchar ) {
tty - > driver - > send_xchar ( tty , ch ) ;
return ;
}
if ( was_stopped )
start_tty ( tty ) ;
tty - > driver - > write ( tty , & ch , 1 ) ;
if ( was_stopped )
stop_tty ( tty ) ;
}
int n_tty_ioctl ( struct tty_struct * tty , struct file * file ,
unsigned int cmd , unsigned long arg )
{
struct tty_struct * real_tty ;
void __user * p = ( void __user * ) arg ;
int retval ;
struct tty_ldisc * ld ;
if ( tty - > driver - > type = = TTY_DRIVER_TYPE_PTY & &
tty - > driver - > subtype = = PTY_TYPE_MASTER )
real_tty = tty - > link ;
else
real_tty = tty ;
switch ( cmd ) {
# ifdef TIOCGETP
case TIOCGETP :
return get_sgttyb ( real_tty , ( struct sgttyb __user * ) arg ) ;
case TIOCSETP :
case TIOCSETN :
return set_sgttyb ( real_tty , ( struct sgttyb __user * ) arg ) ;
# endif
# ifdef TIOCGETC
case TIOCGETC :
return get_tchars ( real_tty , p ) ;
case TIOCSETC :
return set_tchars ( real_tty , p ) ;
# endif
# ifdef TIOCGLTC
case TIOCGLTC :
return get_ltchars ( real_tty , p ) ;
case TIOCSLTC :
return set_ltchars ( real_tty , p ) ;
# endif
case TCGETS :
if ( kernel_termios_to_user_termios ( ( struct termios __user * ) arg , real_tty - > termios ) )
return - EFAULT ;
return 0 ;
case TCSETSF :
return set_termios ( real_tty , p , TERMIOS_FLUSH | TERMIOS_WAIT ) ;
case TCSETSW :
return set_termios ( real_tty , p , TERMIOS_WAIT ) ;
case TCSETS :
return set_termios ( real_tty , p , 0 ) ;
case TCGETA :
return get_termio ( real_tty , p ) ;
case TCSETAF :
return set_termios ( real_tty , p , TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_TERMIO ) ;
case TCSETAW :
return set_termios ( real_tty , p , TERMIOS_WAIT | TERMIOS_TERMIO ) ;
case TCSETA :
return set_termios ( real_tty , p , TERMIOS_TERMIO ) ;
case TCXONC :
retval = tty_check_change ( tty ) ;
if ( retval )
return retval ;
switch ( arg ) {
case TCOOFF :
if ( ! tty - > flow_stopped ) {
tty - > flow_stopped = 1 ;
stop_tty ( tty ) ;
}
break ;
case TCOON :
if ( tty - > flow_stopped ) {
tty - > flow_stopped = 0 ;
start_tty ( tty ) ;
}
break ;
case TCIOFF :
if ( STOP_CHAR ( tty ) ! = __DISABLED_CHAR )
send_prio_char ( tty , STOP_CHAR ( tty ) ) ;
break ;
case TCION :
if ( START_CHAR ( tty ) ! = __DISABLED_CHAR )
send_prio_char ( tty , START_CHAR ( tty ) ) ;
break ;
default :
return - EINVAL ;
}
return 0 ;
case TCFLSH :
retval = tty_check_change ( tty ) ;
if ( retval )
return retval ;
ld = tty_ldisc_ref ( tty ) ;
switch ( arg ) {
case TCIFLUSH :
2005-06-28 20:45:12 -07:00
if ( ld & & ld - > flush_buffer )
2005-04-16 15:20:36 -07:00
ld - > flush_buffer ( tty ) ;
break ;
case TCIOFLUSH :
2005-06-28 20:45:12 -07:00
if ( ld & & ld - > flush_buffer )
2005-04-16 15:20:36 -07:00
ld - > flush_buffer ( tty ) ;
/* fall through */
case TCOFLUSH :
if ( tty - > driver - > flush_buffer )
tty - > driver - > flush_buffer ( tty ) ;
break ;
default :
tty_ldisc_deref ( ld ) ;
return - EINVAL ;
}
tty_ldisc_deref ( ld ) ;
return 0 ;
case TIOCOUTQ :
return put_user ( tty - > driver - > chars_in_buffer ?
tty - > driver - > chars_in_buffer ( tty ) : 0 ,
( int __user * ) arg ) ;
case TIOCINQ :
retval = tty - > read_cnt ;
if ( L_ICANON ( tty ) )
retval = inq_canon ( tty ) ;
return put_user ( retval , ( unsigned int __user * ) arg ) ;
case TIOCGLCKTRMIOS :
if ( kernel_termios_to_user_termios ( ( struct termios __user * ) arg , real_tty - > termios_locked ) )
return - EFAULT ;
return 0 ;
case TIOCSLCKTRMIOS :
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EPERM ;
if ( user_termios_to_kernel_termios ( real_tty - > termios_locked , ( struct termios __user * ) arg ) )
return - EFAULT ;
return 0 ;
case TIOCPKT :
{
int pktmode ;
if ( tty - > driver - > type ! = TTY_DRIVER_TYPE_PTY | |
tty - > driver - > subtype ! = PTY_TYPE_MASTER )
return - ENOTTY ;
if ( get_user ( pktmode , ( int __user * ) arg ) )
return - EFAULT ;
if ( pktmode ) {
if ( ! tty - > packet ) {
tty - > packet = 1 ;
tty - > link - > ctrl_status = 0 ;
}
} else
tty - > packet = 0 ;
return 0 ;
}
case TIOCGSOFTCAR :
return put_user ( C_CLOCAL ( tty ) ? 1 : 0 , ( int __user * ) arg ) ;
case TIOCSSOFTCAR :
if ( get_user ( arg , ( unsigned int __user * ) arg ) )
return - EFAULT ;
down ( & tty - > termios_sem ) ;
tty - > termios - > c_cflag =
( ( tty - > termios - > c_cflag & ~ CLOCAL ) |
( arg ? CLOCAL : 0 ) ) ;
up ( & tty - > termios_sem ) ;
return 0 ;
default :
return - ENOIOCTLCMD ;
}
}
EXPORT_SYMBOL ( n_tty_ioctl ) ;