![hyc@symas.com](/assets/img/avatar_default.png)
This patch is against the 2.6.34 source. Paraphrased from the 1989 BSD patch by David Borman @ cray.com: These are the changes needed for the kernel to support LINEMODE in the server. There is a new bit in the termios local flag word, EXTPROC. When this bit is set, several aspects of the terminal driver are disabled. Input line editing, character echo, and mapping of signals are all disabled. This allows the telnetd to turn off these functions when in linemode, but still keep track of what state the user wants the terminal to be in. New ioctl: TIOCSIG Generate a signal to processes in the current process group of the pty. There is a new mode for packet driver, the TIOCPKT_IOCTL bit. When packet mode is turned on in the pty, and the EXTPROC bit is set, then whenever the state of the pty is changed, the next read on the master side of the pty will have the TIOCPKT_IOCTL bit set. This allows the process on the server side of the pty to know when the state of the terminal has changed; it can then issue the appropriate ioctl to retrieve the new state. Since the original BSD patches accompanied the source code for telnet I've left that reference here, but obviously the feature is useful for any remote terminal protocol, including ssh. The corresponding feature has existed in the BSD tty driver since 1989. For historical reference, a good copy of the relevant files can be found here: http://anonsvn.mit.edu/viewvc/krb5/trunk/src/appl/telnet/?pathrev=17741 Signed-off-by: Howard Chu <hyc@symas.com> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
1180 lines
30 KiB
C
1180 lines
30 KiB
C
/*
|
|
* 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 <linux/mutex.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
|
|
#define TERMIOS_OLD 8
|
|
|
|
|
|
/**
|
|
* tty_chars_in_buffer - characters pending
|
|
* @tty: terminal
|
|
*
|
|
* Return the number of bytes of data in the device private
|
|
* output queue. If no private method is supplied there is assumed
|
|
* to be no queue on the device.
|
|
*/
|
|
|
|
int tty_chars_in_buffer(struct tty_struct *tty)
|
|
{
|
|
if (tty->ops->chars_in_buffer)
|
|
return tty->ops->chars_in_buffer(tty);
|
|
else
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tty_chars_in_buffer);
|
|
|
|
/**
|
|
* tty_write_room - write queue space
|
|
* @tty: terminal
|
|
*
|
|
* Return the number of bytes that can be queued to this device
|
|
* at the present time. The result should be treated as a guarantee
|
|
* and the driver cannot offer a value it later shrinks by more than
|
|
* the number of bytes written. If no method is provided 2K is always
|
|
* returned and data may be lost as there will be no flow control.
|
|
*/
|
|
|
|
int tty_write_room(struct tty_struct *tty)
|
|
{
|
|
if (tty->ops->write_room)
|
|
return tty->ops->write_room(tty);
|
|
return 2048;
|
|
}
|
|
EXPORT_SYMBOL(tty_write_room);
|
|
|
|
/**
|
|
* tty_driver_flush_buffer - discard internal buffer
|
|
* @tty: terminal
|
|
*
|
|
* Discard the internal output buffer for this device. If no method
|
|
* is provided then either the buffer cannot be hardware flushed or
|
|
* there is no buffer driver side.
|
|
*/
|
|
void tty_driver_flush_buffer(struct tty_struct *tty)
|
|
{
|
|
if (tty->ops->flush_buffer)
|
|
tty->ops->flush_buffer(tty);
|
|
}
|
|
EXPORT_SYMBOL(tty_driver_flush_buffer);
|
|
|
|
/**
|
|
* tty_throttle - flow control
|
|
* @tty: terminal
|
|
*
|
|
* Indicate that a tty should stop transmitting data down the stack.
|
|
* Takes the termios mutex to protect against parallel throttle/unthrottle
|
|
* and also to ensure the driver can consistently reference its own
|
|
* termios data at this point when implementing software flow control.
|
|
*/
|
|
|
|
void tty_throttle(struct tty_struct *tty)
|
|
{
|
|
mutex_lock(&tty->termios_mutex);
|
|
/* check TTY_THROTTLED first so it indicates our state */
|
|
if (!test_and_set_bit(TTY_THROTTLED, &tty->flags) &&
|
|
tty->ops->throttle)
|
|
tty->ops->throttle(tty);
|
|
mutex_unlock(&tty->termios_mutex);
|
|
}
|
|
EXPORT_SYMBOL(tty_throttle);
|
|
|
|
/**
|
|
* tty_unthrottle - flow control
|
|
* @tty: terminal
|
|
*
|
|
* Indicate that a tty may continue transmitting data down the stack.
|
|
* Takes the termios mutex to protect against parallel throttle/unthrottle
|
|
* and also to ensure the driver can consistently reference its own
|
|
* termios data at this point when implementing software flow control.
|
|
*
|
|
* Drivers should however remember that the stack can issue a throttle,
|
|
* then change flow control method, then unthrottle.
|
|
*/
|
|
|
|
void tty_unthrottle(struct tty_struct *tty)
|
|
{
|
|
mutex_lock(&tty->termios_mutex);
|
|
if (test_and_clear_bit(TTY_THROTTLED, &tty->flags) &&
|
|
tty->ops->unthrottle)
|
|
tty->ops->unthrottle(tty);
|
|
mutex_unlock(&tty->termios_mutex);
|
|
}
|
|
EXPORT_SYMBOL(tty_unthrottle);
|
|
|
|
/**
|
|
* tty_wait_until_sent - wait for I/O to finish
|
|
* @tty: tty we are waiting for
|
|
* @timeout: how long we will wait
|
|
*
|
|
* Wait for characters pending in a tty driver to hit the wire, or
|
|
* for a timeout to occur (eg due to flow control)
|
|
*
|
|
* Locking: none
|
|
*/
|
|
|
|
void tty_wait_until_sent(struct tty_struct *tty, long timeout)
|
|
{
|
|
#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
|
|
char buf[64];
|
|
|
|
printk(KERN_DEBUG "%s wait until sent...\n", tty_name(tty, buf));
|
|
#endif
|
|
if (!timeout)
|
|
timeout = MAX_SCHEDULE_TIMEOUT;
|
|
if (wait_event_interruptible_timeout(tty->write_wait,
|
|
!tty_chars_in_buffer(tty), timeout) >= 0) {
|
|
if (tty->ops->wait_until_sent)
|
|
tty->ops->wait_until_sent(tty, timeout);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(tty_wait_until_sent);
|
|
|
|
|
|
/*
|
|
* Termios Helper Methods
|
|
*/
|
|
|
|
static void unset_locked_termios(struct ktermios *termios,
|
|
struct ktermios *old,
|
|
struct ktermios *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];
|
|
/* FIXME: What should we do for i/ospeed */
|
|
}
|
|
|
|
/*
|
|
* Routine which returns the baud rate of the tty
|
|
*
|
|
* Note that the baud_table needs to be kept in sync with the
|
|
* include/asm/termbits.h file.
|
|
*/
|
|
static const speed_t baud_table[] = {
|
|
0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
|
9600, 19200, 38400, 57600, 115200, 230400, 460800,
|
|
#ifdef __sparc__
|
|
76800, 153600, 307200, 614400, 921600
|
|
#else
|
|
500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000,
|
|
2500000, 3000000, 3500000, 4000000
|
|
#endif
|
|
};
|
|
|
|
#ifndef __sparc__
|
|
static const tcflag_t baud_bits[] = {
|
|
B0, B50, B75, B110, B134, B150, B200, B300, B600,
|
|
B1200, B1800, B2400, B4800, B9600, B19200, B38400,
|
|
B57600, B115200, B230400, B460800, B500000, B576000,
|
|
B921600, B1000000, B1152000, B1500000, B2000000, B2500000,
|
|
B3000000, B3500000, B4000000
|
|
};
|
|
#else
|
|
static const tcflag_t baud_bits[] = {
|
|
B0, B50, B75, B110, B134, B150, B200, B300, B600,
|
|
B1200, B1800, B2400, B4800, B9600, B19200, B38400,
|
|
B57600, B115200, B230400, B460800, B76800, B153600,
|
|
B307200, B614400, B921600
|
|
};
|
|
#endif
|
|
|
|
static int n_baud_table = ARRAY_SIZE(baud_table);
|
|
|
|
/**
|
|
* tty_termios_baud_rate
|
|
* @termios: termios structure
|
|
*
|
|
* Convert termios baud rate data into a speed. This should be called
|
|
* with the termios lock held if this termios is a terminal termios
|
|
* structure. May change the termios data. Device drivers can call this
|
|
* function but should use ->c_[io]speed directly as they are updated.
|
|
*
|
|
* Locking: none
|
|
*/
|
|
|
|
speed_t tty_termios_baud_rate(struct ktermios *termios)
|
|
{
|
|
unsigned int cbaud;
|
|
|
|
cbaud = termios->c_cflag & CBAUD;
|
|
|
|
#ifdef BOTHER
|
|
/* Magic token for arbitary speed via c_ispeed/c_ospeed */
|
|
if (cbaud == BOTHER)
|
|
return termios->c_ospeed;
|
|
#endif
|
|
if (cbaud & CBAUDEX) {
|
|
cbaud &= ~CBAUDEX;
|
|
|
|
if (cbaud < 1 || cbaud + 15 > n_baud_table)
|
|
termios->c_cflag &= ~CBAUDEX;
|
|
else
|
|
cbaud += 15;
|
|
}
|
|
return baud_table[cbaud];
|
|
}
|
|
EXPORT_SYMBOL(tty_termios_baud_rate);
|
|
|
|
/**
|
|
* tty_termios_input_baud_rate
|
|
* @termios: termios structure
|
|
*
|
|
* Convert termios baud rate data into a speed. This should be called
|
|
* with the termios lock held if this termios is a terminal termios
|
|
* structure. May change the termios data. Device drivers can call this
|
|
* function but should use ->c_[io]speed directly as they are updated.
|
|
*
|
|
* Locking: none
|
|
*/
|
|
|
|
speed_t tty_termios_input_baud_rate(struct ktermios *termios)
|
|
{
|
|
#ifdef IBSHIFT
|
|
unsigned int cbaud = (termios->c_cflag >> IBSHIFT) & CBAUD;
|
|
|
|
if (cbaud == B0)
|
|
return tty_termios_baud_rate(termios);
|
|
|
|
/* Magic token for arbitary speed via c_ispeed*/
|
|
if (cbaud == BOTHER)
|
|
return termios->c_ispeed;
|
|
|
|
if (cbaud & CBAUDEX) {
|
|
cbaud &= ~CBAUDEX;
|
|
|
|
if (cbaud < 1 || cbaud + 15 > n_baud_table)
|
|
termios->c_cflag &= ~(CBAUDEX << IBSHIFT);
|
|
else
|
|
cbaud += 15;
|
|
}
|
|
return baud_table[cbaud];
|
|
#else
|
|
return tty_termios_baud_rate(termios);
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(tty_termios_input_baud_rate);
|
|
|
|
/**
|
|
* tty_termios_encode_baud_rate
|
|
* @termios: ktermios structure holding user requested state
|
|
* @ispeed: input speed
|
|
* @ospeed: output speed
|
|
*
|
|
* Encode the speeds set into the passed termios structure. This is
|
|
* used as a library helper for drivers os that they can report back
|
|
* the actual speed selected when it differs from the speed requested
|
|
*
|
|
* For maximal back compatibility with legacy SYS5/POSIX *nix behaviour
|
|
* we need to carefully set the bits when the user does not get the
|
|
* desired speed. We allow small margins and preserve as much of possible
|
|
* of the input intent to keep compatibility.
|
|
*
|
|
* Locking: Caller should hold termios lock. This is already held
|
|
* when calling this function from the driver termios handler.
|
|
*
|
|
* The ifdefs deal with platforms whose owners have yet to update them
|
|
* and will all go away once this is done.
|
|
*/
|
|
|
|
void tty_termios_encode_baud_rate(struct ktermios *termios,
|
|
speed_t ibaud, speed_t obaud)
|
|
{
|
|
int i = 0;
|
|
int ifound = -1, ofound = -1;
|
|
int iclose = ibaud/50, oclose = obaud/50;
|
|
int ibinput = 0;
|
|
|
|
if (obaud == 0) /* CD dropped */
|
|
ibaud = 0; /* Clear ibaud to be sure */
|
|
|
|
termios->c_ispeed = ibaud;
|
|
termios->c_ospeed = obaud;
|
|
|
|
#ifdef BOTHER
|
|
/* If the user asked for a precise weird speed give a precise weird
|
|
answer. If they asked for a Bfoo speed they many have problems
|
|
digesting non-exact replies so fuzz a bit */
|
|
|
|
if ((termios->c_cflag & CBAUD) == BOTHER)
|
|
oclose = 0;
|
|
if (((termios->c_cflag >> IBSHIFT) & CBAUD) == BOTHER)
|
|
iclose = 0;
|
|
if ((termios->c_cflag >> IBSHIFT) & CBAUD)
|
|
ibinput = 1; /* An input speed was specified */
|
|
#endif
|
|
termios->c_cflag &= ~CBAUD;
|
|
|
|
/*
|
|
* Our goal is to find a close match to the standard baud rate
|
|
* returned. Walk the baud rate table and if we get a very close
|
|
* match then report back the speed as a POSIX Bxxxx value by
|
|
* preference
|
|
*/
|
|
|
|
do {
|
|
if (obaud - oclose <= baud_table[i] &&
|
|
obaud + oclose >= baud_table[i]) {
|
|
termios->c_cflag |= baud_bits[i];
|
|
ofound = i;
|
|
}
|
|
if (ibaud - iclose <= baud_table[i] &&
|
|
ibaud + iclose >= baud_table[i]) {
|
|
/* For the case input == output don't set IBAUD bits
|
|
if the user didn't do so */
|
|
if (ofound == i && !ibinput)
|
|
ifound = i;
|
|
#ifdef IBSHIFT
|
|
else {
|
|
ifound = i;
|
|
termios->c_cflag |= (baud_bits[i] << IBSHIFT);
|
|
}
|
|
#endif
|
|
}
|
|
} while (++i < n_baud_table);
|
|
|
|
/*
|
|
* If we found no match then use BOTHER if provided or warn
|
|
* the user their platform maintainer needs to wake up if not.
|
|
*/
|
|
#ifdef BOTHER
|
|
if (ofound == -1)
|
|
termios->c_cflag |= BOTHER;
|
|
/* Set exact input bits only if the input and output differ or the
|
|
user already did */
|
|
if (ifound == -1 && (ibaud != obaud || ibinput))
|
|
termios->c_cflag |= (BOTHER << IBSHIFT);
|
|
#else
|
|
if (ifound == -1 || ofound == -1) {
|
|
printk_once(KERN_WARNING "tty: Unable to return correct "
|
|
"speed data as your architecture needs updating.\n");
|
|
}
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL_GPL(tty_termios_encode_baud_rate);
|
|
|
|
/**
|
|
* tty_encode_baud_rate - set baud rate of the tty
|
|
* @ibaud: input baud rate
|
|
* @obad: output baud rate
|
|
*
|
|
* Update the current termios data for the tty with the new speed
|
|
* settings. The caller must hold the termios_mutex for the tty in
|
|
* question.
|
|
*/
|
|
|
|
void tty_encode_baud_rate(struct tty_struct *tty, speed_t ibaud, speed_t obaud)
|
|
{
|
|
tty_termios_encode_baud_rate(tty->termios, ibaud, obaud);
|
|
}
|
|
EXPORT_SYMBOL_GPL(tty_encode_baud_rate);
|
|
|
|
/**
|
|
* tty_get_baud_rate - get tty bit rates
|
|
* @tty: tty to query
|
|
*
|
|
* Returns the baud rate as an integer for this terminal. The
|
|
* termios lock must be held by the caller and the terminal bit
|
|
* flags may be updated.
|
|
*
|
|
* Locking: none
|
|
*/
|
|
|
|
speed_t tty_get_baud_rate(struct tty_struct *tty)
|
|
{
|
|
speed_t baud = tty_termios_baud_rate(tty->termios);
|
|
|
|
if (baud == 38400 && tty->alt_speed) {
|
|
if (!tty->warned) {
|
|
printk(KERN_WARNING "Use of setserial/setrocket to "
|
|
"set SPD_* flags is deprecated\n");
|
|
tty->warned = 1;
|
|
}
|
|
baud = tty->alt_speed;
|
|
}
|
|
|
|
return baud;
|
|
}
|
|
EXPORT_SYMBOL(tty_get_baud_rate);
|
|
|
|
/**
|
|
* tty_termios_copy_hw - copy hardware settings
|
|
* @new: New termios
|
|
* @old: Old termios
|
|
*
|
|
* Propogate the hardware specific terminal setting bits from
|
|
* the old termios structure to the new one. This is used in cases
|
|
* where the hardware does not support reconfiguration or as a helper
|
|
* in some cases where only minimal reconfiguration is supported
|
|
*/
|
|
|
|
void tty_termios_copy_hw(struct ktermios *new, struct ktermios *old)
|
|
{
|
|
/* The bits a dumb device handles in software. Smart devices need
|
|
to always provide a set_termios method */
|
|
new->c_cflag &= HUPCL | CREAD | CLOCAL;
|
|
new->c_cflag |= old->c_cflag & ~(HUPCL | CREAD | CLOCAL);
|
|
new->c_ispeed = old->c_ispeed;
|
|
new->c_ospeed = old->c_ospeed;
|
|
}
|
|
EXPORT_SYMBOL(tty_termios_copy_hw);
|
|
|
|
/**
|
|
* tty_termios_hw_change - check for setting change
|
|
* @a: termios
|
|
* @b: termios to compare
|
|
*
|
|
* Check if any of the bits that affect a dumb device have changed
|
|
* between the two termios structures, or a speed change is needed.
|
|
*/
|
|
|
|
int tty_termios_hw_change(struct ktermios *a, struct ktermios *b)
|
|
{
|
|
if (a->c_ispeed != b->c_ispeed || a->c_ospeed != b->c_ospeed)
|
|
return 1;
|
|
if ((a->c_cflag ^ b->c_cflag) & ~(HUPCL | CREAD | CLOCAL))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tty_termios_hw_change);
|
|
|
|
/**
|
|
* change_termios - update termios values
|
|
* @tty: tty to update
|
|
* @new_termios: desired new value
|
|
*
|
|
* Perform updates to the termios values set on this terminal. There
|
|
* is a bit of layering violation here with n_tty in terms of the
|
|
* internal knowledge of this function.
|
|
*
|
|
* Locking: termios_mutex
|
|
*/
|
|
|
|
static void change_termios(struct tty_struct *tty, struct ktermios *new_termios)
|
|
{
|
|
struct ktermios old_termios;
|
|
struct tty_ldisc *ld;
|
|
unsigned long flags;
|
|
|
|
/*
|
|
* Perform the actual termios internal changes under lock.
|
|
*/
|
|
|
|
|
|
/* FIXME: we need to decide on some locking/ordering semantics
|
|
for the set_termios notification eventually */
|
|
mutex_lock(&tty->termios_mutex);
|
|
old_termios = *tty->termios;
|
|
*tty->termios = *new_termios;
|
|
unset_locked_termios(tty->termios, &old_termios, tty->termios_locked);
|
|
|
|
/* See if packet mode change of state. */
|
|
if (tty->link && tty->link->packet) {
|
|
int extproc = (old_termios.c_lflag & EXTPROC) |
|
|
(tty->termios->c_lflag & EXTPROC);
|
|
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) || extproc) {
|
|
spin_lock_irqsave(&tty->ctrl_lock, flags);
|
|
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;
|
|
}
|
|
if (extproc)
|
|
tty->ctrl_status |= TIOCPKT_IOCTL;
|
|
spin_unlock_irqrestore(&tty->ctrl_lock, flags);
|
|
wake_up_interruptible(&tty->link->read_wait);
|
|
}
|
|
}
|
|
|
|
if (tty->ops->set_termios)
|
|
(*tty->ops->set_termios)(tty, &old_termios);
|
|
else
|
|
tty_termios_copy_hw(tty->termios, &old_termios);
|
|
|
|
ld = tty_ldisc_ref(tty);
|
|
if (ld != NULL) {
|
|
if (ld->ops->set_termios)
|
|
(ld->ops->set_termios)(tty, &old_termios);
|
|
tty_ldisc_deref(ld);
|
|
}
|
|
mutex_unlock(&tty->termios_mutex);
|
|
}
|
|
|
|
/**
|
|
* set_termios - set termios values for a tty
|
|
* @tty: terminal device
|
|
* @arg: user data
|
|
* @opt: option information
|
|
*
|
|
* Helper function to prepare termios data and run necessary other
|
|
* functions before using change_termios to do the actual changes.
|
|
*
|
|
* Locking:
|
|
* Called functions take ldisc and termios_mutex locks
|
|
*/
|
|
|
|
static int set_termios(struct tty_struct *tty, void __user *arg, int opt)
|
|
{
|
|
struct ktermios tmp_termios;
|
|
struct tty_ldisc *ld;
|
|
int retval = tty_check_change(tty);
|
|
|
|
if (retval)
|
|
return retval;
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
memcpy(&tmp_termios, tty->termios, sizeof(struct ktermios));
|
|
mutex_unlock(&tty->termios_mutex);
|
|
|
|
if (opt & TERMIOS_TERMIO) {
|
|
if (user_termio_to_kernel_termios(&tmp_termios,
|
|
(struct termio __user *)arg))
|
|
return -EFAULT;
|
|
#ifdef TCGETS2
|
|
} else if (opt & TERMIOS_OLD) {
|
|
if (user_termios_to_kernel_termios_1(&tmp_termios,
|
|
(struct termios __user *)arg))
|
|
return -EFAULT;
|
|
} else {
|
|
if (user_termios_to_kernel_termios(&tmp_termios,
|
|
(struct termios2 __user *)arg))
|
|
return -EFAULT;
|
|
}
|
|
#else
|
|
} else if (user_termios_to_kernel_termios(&tmp_termios,
|
|
(struct termios __user *)arg))
|
|
return -EFAULT;
|
|
#endif
|
|
|
|
/* If old style Bfoo values are used then load c_ispeed/c_ospeed
|
|
* with the real speed so its unconditionally usable */
|
|
tmp_termios.c_ispeed = tty_termios_input_baud_rate(&tmp_termios);
|
|
tmp_termios.c_ospeed = tty_termios_baud_rate(&tmp_termios);
|
|
|
|
ld = tty_ldisc_ref(tty);
|
|
|
|
if (ld != NULL) {
|
|
if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer)
|
|
ld->ops->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);
|
|
|
|
/* FIXME: Arguably if tmp_termios == tty->termios AND the
|
|
actual requested termios was not tmp_termios then we may
|
|
want to return an error as no user requested change has
|
|
succeeded */
|
|
return 0;
|
|
}
|
|
|
|
static void copy_termios(struct tty_struct *tty, struct ktermios *kterm)
|
|
{
|
|
mutex_lock(&tty->termios_mutex);
|
|
memcpy(kterm, tty->termios, sizeof(struct ktermios));
|
|
mutex_unlock(&tty->termios_mutex);
|
|
}
|
|
|
|
static void copy_termios_locked(struct tty_struct *tty, struct ktermios *kterm)
|
|
{
|
|
mutex_lock(&tty->termios_mutex);
|
|
memcpy(kterm, tty->termios_locked, sizeof(struct ktermios));
|
|
mutex_unlock(&tty->termios_mutex);
|
|
}
|
|
|
|
static int get_termio(struct tty_struct *tty, struct termio __user *termio)
|
|
{
|
|
struct ktermios kterm;
|
|
copy_termios(tty, &kterm);
|
|
if (kernel_termios_to_user_termio(termio, &kterm))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef TCGETX
|
|
|
|
/**
|
|
* set_termiox - set termiox fields if possible
|
|
* @tty: terminal
|
|
* @arg: termiox structure from user
|
|
* @opt: option flags for ioctl type
|
|
*
|
|
* Implement the device calling points for the SYS5 termiox ioctl
|
|
* interface in Linux
|
|
*/
|
|
|
|
static int set_termiox(struct tty_struct *tty, void __user *arg, int opt)
|
|
{
|
|
struct termiox tnew;
|
|
struct tty_ldisc *ld;
|
|
|
|
if (tty->termiox == NULL)
|
|
return -EINVAL;
|
|
if (copy_from_user(&tnew, arg, sizeof(struct termiox)))
|
|
return -EFAULT;
|
|
|
|
ld = tty_ldisc_ref(tty);
|
|
if (ld != NULL) {
|
|
if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer)
|
|
ld->ops->flush_buffer(tty);
|
|
tty_ldisc_deref(ld);
|
|
}
|
|
if (opt & TERMIOS_WAIT) {
|
|
tty_wait_until_sent(tty, 0);
|
|
if (signal_pending(current))
|
|
return -EINTR;
|
|
}
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
if (tty->ops->set_termiox)
|
|
tty->ops->set_termiox(tty, &tnew);
|
|
mutex_unlock(&tty->termios_mutex);
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#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;
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
tmp.sg_ispeed = tty->termios->c_ispeed;
|
|
tmp.sg_ospeed = tty->termios->c_ospeed;
|
|
tmp.sg_erase = tty->termios->c_cc[VERASE];
|
|
tmp.sg_kill = tty->termios->c_cc[VKILL];
|
|
tmp.sg_flags = get_sgflags(tty);
|
|
mutex_unlock(&tty->termios_mutex);
|
|
|
|
return copy_to_user(sgttyb, &tmp, sizeof(tmp)) ? -EFAULT : 0;
|
|
}
|
|
|
|
static void set_sgflags(struct ktermios *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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* set_sgttyb - set legacy terminal values
|
|
* @tty: tty structure
|
|
* @sgttyb: pointer to old style terminal structure
|
|
*
|
|
* Updates a terminal from the legacy BSD style terminal information
|
|
* structure.
|
|
*
|
|
* Locking: termios_mutex
|
|
*/
|
|
|
|
static int set_sgttyb(struct tty_struct *tty, struct sgttyb __user *sgttyb)
|
|
{
|
|
int retval;
|
|
struct sgttyb tmp;
|
|
struct ktermios termios;
|
|
|
|
retval = tty_check_change(tty);
|
|
if (retval)
|
|
return retval;
|
|
|
|
if (copy_from_user(&tmp, sgttyb, sizeof(tmp)))
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
termios = *tty->termios;
|
|
termios.c_cc[VERASE] = tmp.sg_erase;
|
|
termios.c_cc[VKILL] = tmp.sg_kill;
|
|
set_sgflags(&termios, tmp.sg_flags);
|
|
/* Try and encode into Bfoo format */
|
|
#ifdef BOTHER
|
|
tty_termios_encode_baud_rate(&termios, termios.c_ispeed,
|
|
termios.c_ospeed);
|
|
#endif
|
|
mutex_unlock(&tty->termios_mutex);
|
|
change_termios(tty, &termios);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef TIOCGETC
|
|
static int get_tchars(struct tty_struct *tty, struct tchars __user *tchars)
|
|
{
|
|
struct tchars tmp;
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
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? */
|
|
mutex_unlock(&tty->termios_mutex);
|
|
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;
|
|
mutex_lock(&tty->termios_mutex);
|
|
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? */
|
|
mutex_unlock(&tty->termios_mutex);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef TIOCGLTC
|
|
static int get_ltchars(struct tty_struct *tty, struct ltchars __user *ltchars)
|
|
{
|
|
struct ltchars tmp;
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
tmp.t_suspc = tty->termios->c_cc[VSUSP];
|
|
/* what is dsuspc anyway? */
|
|
tmp.t_dsuspc = tty->termios->c_cc[VSUSP];
|
|
tmp.t_rprntc = tty->termios->c_cc[VREPRINT];
|
|
/* what is flushc anyway? */
|
|
tmp.t_flushc = tty->termios->c_cc[VEOL2];
|
|
tmp.t_werasc = tty->termios->c_cc[VWERASE];
|
|
tmp.t_lnextc = tty->termios->c_cc[VLNEXT];
|
|
mutex_unlock(&tty->termios_mutex);
|
|
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;
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
tty->termios->c_cc[VSUSP] = tmp.t_suspc;
|
|
/* what is dsuspc anyway? */
|
|
tty->termios->c_cc[VEOL2] = tmp.t_dsuspc;
|
|
tty->termios->c_cc[VREPRINT] = tmp.t_rprntc;
|
|
/* what is flushc anyway? */
|
|
tty->termios->c_cc[VEOL2] = tmp.t_flushc;
|
|
tty->termios->c_cc[VWERASE] = tmp.t_werasc;
|
|
tty->termios->c_cc[VLNEXT] = tmp.t_lnextc;
|
|
mutex_unlock(&tty->termios_mutex);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* send_prio_char - send priority character
|
|
*
|
|
* Send a high priority character to the tty even if stopped
|
|
*
|
|
* Locking: none for xchar method, write ordering for write method.
|
|
*/
|
|
|
|
static int send_prio_char(struct tty_struct *tty, char ch)
|
|
{
|
|
int was_stopped = tty->stopped;
|
|
|
|
if (tty->ops->send_xchar) {
|
|
tty->ops->send_xchar(tty, ch);
|
|
return 0;
|
|
}
|
|
|
|
if (tty_write_lock(tty, 0) < 0)
|
|
return -ERESTARTSYS;
|
|
|
|
if (was_stopped)
|
|
start_tty(tty);
|
|
tty->ops->write(tty, &ch, 1);
|
|
if (was_stopped)
|
|
stop_tty(tty);
|
|
tty_write_unlock(tty);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tty_change_softcar - carrier change ioctl helper
|
|
* @tty: tty to update
|
|
* @arg: enable/disable CLOCAL
|
|
*
|
|
* Perform a change to the CLOCAL state and call into the driver
|
|
* layer to make it visible. All done with the termios mutex
|
|
*/
|
|
|
|
static int tty_change_softcar(struct tty_struct *tty, int arg)
|
|
{
|
|
int ret = 0;
|
|
int bit = arg ? CLOCAL : 0;
|
|
struct ktermios old;
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
old = *tty->termios;
|
|
tty->termios->c_cflag &= ~CLOCAL;
|
|
tty->termios->c_cflag |= bit;
|
|
if (tty->ops->set_termios)
|
|
tty->ops->set_termios(tty, &old);
|
|
if ((tty->termios->c_cflag & CLOCAL) != bit)
|
|
ret = -EINVAL;
|
|
mutex_unlock(&tty->termios_mutex);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tty_mode_ioctl - mode related ioctls
|
|
* @tty: tty for the ioctl
|
|
* @file: file pointer for the tty
|
|
* @cmd: command
|
|
* @arg: ioctl argument
|
|
*
|
|
* Perform non line discipline specific mode control ioctls. This
|
|
* is designed to be called by line disciplines to ensure they provide
|
|
* consistent mode setting.
|
|
*/
|
|
|
|
int tty_mode_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 ret = 0;
|
|
struct ktermios kterm;
|
|
|
|
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 TCSETSF:
|
|
return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_OLD);
|
|
case TCSETSW:
|
|
return set_termios(real_tty, p, TERMIOS_WAIT | TERMIOS_OLD);
|
|
case TCSETS:
|
|
return set_termios(real_tty, p, TERMIOS_OLD);
|
|
#ifndef TCGETS2
|
|
case TCGETS:
|
|
copy_termios(real_tty, &kterm);
|
|
if (kernel_termios_to_user_termios((struct termios __user *)arg, &kterm))
|
|
ret = -EFAULT;
|
|
return ret;
|
|
#else
|
|
case TCGETS:
|
|
copy_termios(real_tty, &kterm);
|
|
if (kernel_termios_to_user_termios_1((struct termios __user *)arg, &kterm))
|
|
ret = -EFAULT;
|
|
return ret;
|
|
case TCGETS2:
|
|
copy_termios(real_tty, &kterm);
|
|
if (kernel_termios_to_user_termios((struct termios2 __user *)arg, &kterm))
|
|
ret = -EFAULT;
|
|
return ret;
|
|
case TCSETSF2:
|
|
return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT);
|
|
case TCSETSW2:
|
|
return set_termios(real_tty, p, TERMIOS_WAIT);
|
|
case TCSETS2:
|
|
return set_termios(real_tty, p, 0);
|
|
#endif
|
|
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);
|
|
#ifndef TCGETS2
|
|
case TIOCGLCKTRMIOS:
|
|
copy_termios_locked(real_tty, &kterm);
|
|
if (kernel_termios_to_user_termios((struct termios __user *)arg, &kterm))
|
|
ret = -EFAULT;
|
|
return ret;
|
|
case TIOCSLCKTRMIOS:
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
copy_termios_locked(real_tty, &kterm);
|
|
if (user_termios_to_kernel_termios(&kterm,
|
|
(struct termios __user *) arg))
|
|
return -EFAULT;
|
|
mutex_lock(&real_tty->termios_mutex);
|
|
memcpy(real_tty->termios_locked, &kterm, sizeof(struct ktermios));
|
|
mutex_unlock(&real_tty->termios_mutex);
|
|
return 0;
|
|
#else
|
|
case TIOCGLCKTRMIOS:
|
|
copy_termios_locked(real_tty, &kterm);
|
|
if (kernel_termios_to_user_termios_1((struct termios __user *)arg, &kterm))
|
|
ret = -EFAULT;
|
|
return ret;
|
|
case TIOCSLCKTRMIOS:
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
copy_termios_locked(real_tty, &kterm);
|
|
if (user_termios_to_kernel_termios_1(&kterm,
|
|
(struct termios __user *) arg))
|
|
return -EFAULT;
|
|
mutex_lock(&real_tty->termios_mutex);
|
|
memcpy(real_tty->termios_locked, &kterm, sizeof(struct ktermios));
|
|
mutex_unlock(&real_tty->termios_mutex);
|
|
return ret;
|
|
#endif
|
|
#ifdef TCGETX
|
|
case TCGETX: {
|
|
struct termiox ktermx;
|
|
if (real_tty->termiox == NULL)
|
|
return -EINVAL;
|
|
mutex_lock(&real_tty->termios_mutex);
|
|
memcpy(&ktermx, real_tty->termiox, sizeof(struct termiox));
|
|
mutex_unlock(&real_tty->termios_mutex);
|
|
if (copy_to_user(p, &ktermx, sizeof(struct termiox)))
|
|
ret = -EFAULT;
|
|
return ret;
|
|
}
|
|
case TCSETX:
|
|
return set_termiox(real_tty, p, 0);
|
|
case TCSETXW:
|
|
return set_termiox(real_tty, p, TERMIOS_WAIT);
|
|
case TCSETXF:
|
|
return set_termiox(real_tty, p, TERMIOS_FLUSH);
|
|
#endif
|
|
case TIOCGSOFTCAR:
|
|
copy_termios(real_tty, &kterm);
|
|
ret = put_user((kterm.c_cflag & CLOCAL) ? 1 : 0,
|
|
(int __user *)arg);
|
|
return ret;
|
|
case TIOCSSOFTCAR:
|
|
if (get_user(arg, (unsigned int __user *) arg))
|
|
return -EFAULT;
|
|
return tty_change_softcar(real_tty, arg);
|
|
default:
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(tty_mode_ioctl);
|
|
|
|
int tty_perform_flush(struct tty_struct *tty, unsigned long arg)
|
|
{
|
|
struct tty_ldisc *ld;
|
|
int retval = tty_check_change(tty);
|
|
if (retval)
|
|
return retval;
|
|
|
|
ld = tty_ldisc_ref_wait(tty);
|
|
switch (arg) {
|
|
case TCIFLUSH:
|
|
if (ld && ld->ops->flush_buffer)
|
|
ld->ops->flush_buffer(tty);
|
|
break;
|
|
case TCIOFLUSH:
|
|
if (ld && ld->ops->flush_buffer)
|
|
ld->ops->flush_buffer(tty);
|
|
/* fall through */
|
|
case TCOFLUSH:
|
|
tty_driver_flush_buffer(tty);
|
|
break;
|
|
default:
|
|
tty_ldisc_deref(ld);
|
|
return -EINVAL;
|
|
}
|
|
tty_ldisc_deref(ld);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tty_perform_flush);
|
|
|
|
int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
unsigned long flags;
|
|
int retval;
|
|
|
|
switch (cmd) {
|
|
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)
|
|
return send_prio_char(tty, STOP_CHAR(tty));
|
|
break;
|
|
case TCION:
|
|
if (START_CHAR(tty) != __DISABLED_CHAR)
|
|
return send_prio_char(tty, START_CHAR(tty));
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
case TCFLSH:
|
|
return tty_perform_flush(tty, arg);
|
|
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;
|
|
spin_lock_irqsave(&tty->ctrl_lock, flags);
|
|
if (pktmode) {
|
|
if (!tty->packet) {
|
|
tty->packet = 1;
|
|
tty->link->ctrl_status = 0;
|
|
}
|
|
} else
|
|
tty->packet = 0;
|
|
spin_unlock_irqrestore(&tty->ctrl_lock, flags);
|
|
return 0;
|
|
}
|
|
default:
|
|
/* Try the mode commands */
|
|
return tty_mode_ioctl(tty, file, cmd, arg);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(n_tty_ioctl_helper);
|