TTY: create drivers/tty and move the tty core files there
The tty code should be in its own subdirectory and not in the char driver with all of the cruft that is currently there. Based on work done by Arnd Bergmann <arnd@arndb.de> Acked-by: Arnd Bergmann <arnd@arndb.de> Cc: Jiri Slaby <jslaby@suse.cz> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
9
drivers/tty/Makefile
Normal file
9
drivers/tty/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
obj-y += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \
|
||||
tty_buffer.o tty_port.o tty_mutex.o
|
||||
obj-$(CONFIG_LEGACY_PTYS) += pty.o
|
||||
obj-$(CONFIG_UNIX98_PTYS) += pty.o
|
||||
obj-$(CONFIG_AUDIT) += tty_audit.o
|
||||
obj-$(CONFIG_MAGIC_SYSRQ) += sysrq.o
|
||||
obj-$(CONFIG_N_HDLC) += n_hdlc.o
|
||||
obj-$(CONFIG_N_GSM) += n_gsm.o
|
||||
obj-$(CONFIG_R3964) += n_r3964.o
|
2763
drivers/tty/n_gsm.c
Normal file
2763
drivers/tty/n_gsm.c
Normal file
File diff suppressed because it is too large
Load Diff
1007
drivers/tty/n_hdlc.c
Normal file
1007
drivers/tty/n_hdlc.c
Normal file
File diff suppressed because it is too large
Load Diff
1264
drivers/tty/n_r3964.c
Normal file
1264
drivers/tty/n_r3964.c
Normal file
File diff suppressed because it is too large
Load Diff
2121
drivers/tty/n_tty.c
Normal file
2121
drivers/tty/n_tty.c
Normal file
File diff suppressed because it is too large
Load Diff
777
drivers/tty/pty.c
Normal file
777
drivers/tty/pty.c
Normal file
@@ -0,0 +1,777 @@
|
||||
/*
|
||||
* linux/drivers/char/pty.c
|
||||
*
|
||||
* Copyright (C) 1991, 1992 Linus Torvalds
|
||||
*
|
||||
* Added support for a Unix98-style ptmx device.
|
||||
* -- C. Scott Ananian <cananian@alumni.princeton.edu>, 14-Jan-1998
|
||||
*
|
||||
* When reading this code see also fs/devpts. In particular note that the
|
||||
* driver_data field is used by the devpts side as a binding to the devpts
|
||||
* inode.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_flip.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/major.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/sysctl.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/devpts_fs.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/system.h>
|
||||
|
||||
#ifdef CONFIG_UNIX98_PTYS
|
||||
static struct tty_driver *ptm_driver;
|
||||
static struct tty_driver *pts_driver;
|
||||
#endif
|
||||
|
||||
static void pty_close(struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
BUG_ON(!tty);
|
||||
if (tty->driver->subtype == PTY_TYPE_MASTER)
|
||||
WARN_ON(tty->count > 1);
|
||||
else {
|
||||
if (tty->count > 2)
|
||||
return;
|
||||
}
|
||||
wake_up_interruptible(&tty->read_wait);
|
||||
wake_up_interruptible(&tty->write_wait);
|
||||
tty->packet = 0;
|
||||
if (!tty->link)
|
||||
return;
|
||||
tty->link->packet = 0;
|
||||
set_bit(TTY_OTHER_CLOSED, &tty->link->flags);
|
||||
wake_up_interruptible(&tty->link->read_wait);
|
||||
wake_up_interruptible(&tty->link->write_wait);
|
||||
if (tty->driver->subtype == PTY_TYPE_MASTER) {
|
||||
set_bit(TTY_OTHER_CLOSED, &tty->flags);
|
||||
#ifdef CONFIG_UNIX98_PTYS
|
||||
if (tty->driver == ptm_driver)
|
||||
devpts_pty_kill(tty->link);
|
||||
#endif
|
||||
tty_unlock();
|
||||
tty_vhangup(tty->link);
|
||||
tty_lock();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The unthrottle routine is called by the line discipline to signal
|
||||
* that it can receive more characters. For PTY's, the TTY_THROTTLED
|
||||
* flag is always set, to force the line discipline to always call the
|
||||
* unthrottle routine when there are fewer than TTY_THRESHOLD_UNTHROTTLE
|
||||
* characters in the queue. This is necessary since each time this
|
||||
* happens, we need to wake up any sleeping processes that could be
|
||||
* (1) trying to send data to the pty, or (2) waiting in wait_until_sent()
|
||||
* for the pty buffer to be drained.
|
||||
*/
|
||||
static void pty_unthrottle(struct tty_struct *tty)
|
||||
{
|
||||
tty_wakeup(tty->link);
|
||||
set_bit(TTY_THROTTLED, &tty->flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* pty_space - report space left for writing
|
||||
* @to: tty we are writing into
|
||||
*
|
||||
* The tty buffers allow 64K but we sneak a peak and clip at 8K this
|
||||
* allows a lot of overspill room for echo and other fun messes to
|
||||
* be handled properly
|
||||
*/
|
||||
|
||||
static int pty_space(struct tty_struct *to)
|
||||
{
|
||||
int n = 8192 - to->buf.memory_used;
|
||||
if (n < 0)
|
||||
return 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* pty_write - write to a pty
|
||||
* @tty: the tty we write from
|
||||
* @buf: kernel buffer of data
|
||||
* @count: bytes to write
|
||||
*
|
||||
* Our "hardware" write method. Data is coming from the ldisc which
|
||||
* may be in a non sleeping state. We simply throw this at the other
|
||||
* end of the link as if we were an IRQ handler receiving stuff for
|
||||
* the other side of the pty/tty pair.
|
||||
*/
|
||||
|
||||
static int pty_write(struct tty_struct *tty, const unsigned char *buf, int c)
|
||||
{
|
||||
struct tty_struct *to = tty->link;
|
||||
|
||||
if (tty->stopped)
|
||||
return 0;
|
||||
|
||||
if (c > 0) {
|
||||
/* Stuff the data into the input queue of the other end */
|
||||
c = tty_insert_flip_string(to, buf, c);
|
||||
/* And shovel */
|
||||
if (c) {
|
||||
tty_flip_buffer_push(to);
|
||||
tty_wakeup(tty);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* pty_write_room - write space
|
||||
* @tty: tty we are writing from
|
||||
*
|
||||
* Report how many bytes the ldisc can send into the queue for
|
||||
* the other device.
|
||||
*/
|
||||
|
||||
static int pty_write_room(struct tty_struct *tty)
|
||||
{
|
||||
if (tty->stopped)
|
||||
return 0;
|
||||
return pty_space(tty->link);
|
||||
}
|
||||
|
||||
/**
|
||||
* pty_chars_in_buffer - characters currently in our tx queue
|
||||
* @tty: our tty
|
||||
*
|
||||
* Report how much we have in the transmit queue. As everything is
|
||||
* instantly at the other end this is easy to implement.
|
||||
*/
|
||||
|
||||
static int pty_chars_in_buffer(struct tty_struct *tty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set the lock flag on a pty */
|
||||
static int pty_set_lock(struct tty_struct *tty, int __user *arg)
|
||||
{
|
||||
int val;
|
||||
if (get_user(val, arg))
|
||||
return -EFAULT;
|
||||
if (val)
|
||||
set_bit(TTY_PTY_LOCK, &tty->flags);
|
||||
else
|
||||
clear_bit(TTY_PTY_LOCK, &tty->flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Send a signal to the slave */
|
||||
static int pty_signal(struct tty_struct *tty, int sig)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct pid *pgrp;
|
||||
|
||||
if (tty->link) {
|
||||
spin_lock_irqsave(&tty->link->ctrl_lock, flags);
|
||||
pgrp = get_pid(tty->link->pgrp);
|
||||
spin_unlock_irqrestore(&tty->link->ctrl_lock, flags);
|
||||
|
||||
kill_pgrp(pgrp, sig, 1);
|
||||
put_pid(pgrp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pty_flush_buffer(struct tty_struct *tty)
|
||||
{
|
||||
struct tty_struct *to = tty->link;
|
||||
unsigned long flags;
|
||||
|
||||
if (!to)
|
||||
return;
|
||||
/* tty_buffer_flush(to); FIXME */
|
||||
if (to->packet) {
|
||||
spin_lock_irqsave(&tty->ctrl_lock, flags);
|
||||
tty->ctrl_status |= TIOCPKT_FLUSHWRITE;
|
||||
wake_up_interruptible(&to->read_wait);
|
||||
spin_unlock_irqrestore(&tty->ctrl_lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static int pty_open(struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
int retval = -ENODEV;
|
||||
|
||||
if (!tty || !tty->link)
|
||||
goto out;
|
||||
|
||||
retval = -EIO;
|
||||
if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
|
||||
goto out;
|
||||
if (test_bit(TTY_PTY_LOCK, &tty->link->flags))
|
||||
goto out;
|
||||
if (tty->link->count != 1)
|
||||
goto out;
|
||||
|
||||
clear_bit(TTY_OTHER_CLOSED, &tty->link->flags);
|
||||
set_bit(TTY_THROTTLED, &tty->flags);
|
||||
retval = 0;
|
||||
out:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void pty_set_termios(struct tty_struct *tty,
|
||||
struct ktermios *old_termios)
|
||||
{
|
||||
tty->termios->c_cflag &= ~(CSIZE | PARENB);
|
||||
tty->termios->c_cflag |= (CS8 | CREAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* pty_do_resize - resize event
|
||||
* @tty: tty being resized
|
||||
* @ws: window size being set.
|
||||
*
|
||||
* Update the termios variables and send the necessary signals to
|
||||
* peform a terminal resize correctly
|
||||
*/
|
||||
|
||||
int pty_resize(struct tty_struct *tty, struct winsize *ws)
|
||||
{
|
||||
struct pid *pgrp, *rpgrp;
|
||||
unsigned long flags;
|
||||
struct tty_struct *pty = tty->link;
|
||||
|
||||
/* For a PTY we need to lock the tty side */
|
||||
mutex_lock(&tty->termios_mutex);
|
||||
if (!memcmp(ws, &tty->winsize, sizeof(*ws)))
|
||||
goto done;
|
||||
|
||||
/* Get the PID values and reference them so we can
|
||||
avoid holding the tty ctrl lock while sending signals.
|
||||
We need to lock these individually however. */
|
||||
|
||||
spin_lock_irqsave(&tty->ctrl_lock, flags);
|
||||
pgrp = get_pid(tty->pgrp);
|
||||
spin_unlock_irqrestore(&tty->ctrl_lock, flags);
|
||||
|
||||
spin_lock_irqsave(&pty->ctrl_lock, flags);
|
||||
rpgrp = get_pid(pty->pgrp);
|
||||
spin_unlock_irqrestore(&pty->ctrl_lock, flags);
|
||||
|
||||
if (pgrp)
|
||||
kill_pgrp(pgrp, SIGWINCH, 1);
|
||||
if (rpgrp != pgrp && rpgrp)
|
||||
kill_pgrp(rpgrp, SIGWINCH, 1);
|
||||
|
||||
put_pid(pgrp);
|
||||
put_pid(rpgrp);
|
||||
|
||||
tty->winsize = *ws;
|
||||
pty->winsize = *ws; /* Never used so will go away soon */
|
||||
done:
|
||||
mutex_unlock(&tty->termios_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Traditional BSD devices */
|
||||
#ifdef CONFIG_LEGACY_PTYS
|
||||
|
||||
static int pty_install(struct tty_driver *driver, struct tty_struct *tty)
|
||||
{
|
||||
struct tty_struct *o_tty;
|
||||
int idx = tty->index;
|
||||
int retval;
|
||||
|
||||
o_tty = alloc_tty_struct();
|
||||
if (!o_tty)
|
||||
return -ENOMEM;
|
||||
if (!try_module_get(driver->other->owner)) {
|
||||
/* This cannot in fact currently happen */
|
||||
free_tty_struct(o_tty);
|
||||
return -ENOMEM;
|
||||
}
|
||||
initialize_tty_struct(o_tty, driver->other, idx);
|
||||
|
||||
/* We always use new tty termios data so we can do this
|
||||
the easy way .. */
|
||||
retval = tty_init_termios(tty);
|
||||
if (retval)
|
||||
goto free_mem_out;
|
||||
|
||||
retval = tty_init_termios(o_tty);
|
||||
if (retval) {
|
||||
tty_free_termios(tty);
|
||||
goto free_mem_out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Everything allocated ... set up the o_tty structure.
|
||||
*/
|
||||
driver->other->ttys[idx] = o_tty;
|
||||
tty_driver_kref_get(driver->other);
|
||||
if (driver->subtype == PTY_TYPE_MASTER)
|
||||
o_tty->count++;
|
||||
/* Establish the links in both directions */
|
||||
tty->link = o_tty;
|
||||
o_tty->link = tty;
|
||||
|
||||
tty_driver_kref_get(driver);
|
||||
tty->count++;
|
||||
driver->ttys[idx] = tty;
|
||||
return 0;
|
||||
free_mem_out:
|
||||
module_put(o_tty->driver->owner);
|
||||
free_tty_struct(o_tty);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static int pty_bsd_ioctl(struct tty_struct *tty, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case TIOCSPTLCK: /* Set PT Lock (disallow slave open) */
|
||||
return pty_set_lock(tty, (int __user *) arg);
|
||||
case TIOCSIG: /* Send signal to other side of pty */
|
||||
return pty_signal(tty, (int) arg);
|
||||
}
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
static int legacy_count = CONFIG_LEGACY_PTY_COUNT;
|
||||
module_param(legacy_count, int, 0);
|
||||
|
||||
/*
|
||||
* The master side of a pty can do TIOCSPTLCK and thus
|
||||
* has pty_bsd_ioctl.
|
||||
*/
|
||||
static const struct tty_operations master_pty_ops_bsd = {
|
||||
.install = pty_install,
|
||||
.open = pty_open,
|
||||
.close = pty_close,
|
||||
.write = pty_write,
|
||||
.write_room = pty_write_room,
|
||||
.flush_buffer = pty_flush_buffer,
|
||||
.chars_in_buffer = pty_chars_in_buffer,
|
||||
.unthrottle = pty_unthrottle,
|
||||
.set_termios = pty_set_termios,
|
||||
.ioctl = pty_bsd_ioctl,
|
||||
.resize = pty_resize
|
||||
};
|
||||
|
||||
static const struct tty_operations slave_pty_ops_bsd = {
|
||||
.install = pty_install,
|
||||
.open = pty_open,
|
||||
.close = pty_close,
|
||||
.write = pty_write,
|
||||
.write_room = pty_write_room,
|
||||
.flush_buffer = pty_flush_buffer,
|
||||
.chars_in_buffer = pty_chars_in_buffer,
|
||||
.unthrottle = pty_unthrottle,
|
||||
.set_termios = pty_set_termios,
|
||||
.resize = pty_resize
|
||||
};
|
||||
|
||||
static void __init legacy_pty_init(void)
|
||||
{
|
||||
struct tty_driver *pty_driver, *pty_slave_driver;
|
||||
|
||||
if (legacy_count <= 0)
|
||||
return;
|
||||
|
||||
pty_driver = alloc_tty_driver(legacy_count);
|
||||
if (!pty_driver)
|
||||
panic("Couldn't allocate pty driver");
|
||||
|
||||
pty_slave_driver = alloc_tty_driver(legacy_count);
|
||||
if (!pty_slave_driver)
|
||||
panic("Couldn't allocate pty slave driver");
|
||||
|
||||
pty_driver->owner = THIS_MODULE;
|
||||
pty_driver->driver_name = "pty_master";
|
||||
pty_driver->name = "pty";
|
||||
pty_driver->major = PTY_MASTER_MAJOR;
|
||||
pty_driver->minor_start = 0;
|
||||
pty_driver->type = TTY_DRIVER_TYPE_PTY;
|
||||
pty_driver->subtype = PTY_TYPE_MASTER;
|
||||
pty_driver->init_termios = tty_std_termios;
|
||||
pty_driver->init_termios.c_iflag = 0;
|
||||
pty_driver->init_termios.c_oflag = 0;
|
||||
pty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
|
||||
pty_driver->init_termios.c_lflag = 0;
|
||||
pty_driver->init_termios.c_ispeed = 38400;
|
||||
pty_driver->init_termios.c_ospeed = 38400;
|
||||
pty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW;
|
||||
pty_driver->other = pty_slave_driver;
|
||||
tty_set_operations(pty_driver, &master_pty_ops_bsd);
|
||||
|
||||
pty_slave_driver->owner = THIS_MODULE;
|
||||
pty_slave_driver->driver_name = "pty_slave";
|
||||
pty_slave_driver->name = "ttyp";
|
||||
pty_slave_driver->major = PTY_SLAVE_MAJOR;
|
||||
pty_slave_driver->minor_start = 0;
|
||||
pty_slave_driver->type = TTY_DRIVER_TYPE_PTY;
|
||||
pty_slave_driver->subtype = PTY_TYPE_SLAVE;
|
||||
pty_slave_driver->init_termios = tty_std_termios;
|
||||
pty_slave_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
|
||||
pty_slave_driver->init_termios.c_ispeed = 38400;
|
||||
pty_slave_driver->init_termios.c_ospeed = 38400;
|
||||
pty_slave_driver->flags = TTY_DRIVER_RESET_TERMIOS |
|
||||
TTY_DRIVER_REAL_RAW;
|
||||
pty_slave_driver->other = pty_driver;
|
||||
tty_set_operations(pty_slave_driver, &slave_pty_ops_bsd);
|
||||
|
||||
if (tty_register_driver(pty_driver))
|
||||
panic("Couldn't register pty driver");
|
||||
if (tty_register_driver(pty_slave_driver))
|
||||
panic("Couldn't register pty slave driver");
|
||||
}
|
||||
#else
|
||||
static inline void legacy_pty_init(void) { }
|
||||
#endif
|
||||
|
||||
/* Unix98 devices */
|
||||
#ifdef CONFIG_UNIX98_PTYS
|
||||
/*
|
||||
* sysctl support for setting limits on the number of Unix98 ptys allocated.
|
||||
* Otherwise one can eat up all kernel memory by opening /dev/ptmx repeatedly.
|
||||
*/
|
||||
int pty_limit = NR_UNIX98_PTY_DEFAULT;
|
||||
static int pty_limit_min;
|
||||
static int pty_limit_max = NR_UNIX98_PTY_MAX;
|
||||
static int pty_count;
|
||||
|
||||
static struct cdev ptmx_cdev;
|
||||
|
||||
static struct ctl_table pty_table[] = {
|
||||
{
|
||||
.procname = "max",
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.data = &pty_limit,
|
||||
.proc_handler = proc_dointvec_minmax,
|
||||
.extra1 = &pty_limit_min,
|
||||
.extra2 = &pty_limit_max,
|
||||
}, {
|
||||
.procname = "nr",
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0444,
|
||||
.data = &pty_count,
|
||||
.proc_handler = proc_dointvec,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct ctl_table pty_kern_table[] = {
|
||||
{
|
||||
.procname = "pty",
|
||||
.mode = 0555,
|
||||
.child = pty_table,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct ctl_table pty_root_table[] = {
|
||||
{
|
||||
.procname = "kernel",
|
||||
.mode = 0555,
|
||||
.child = pty_kern_table,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
static int pty_unix98_ioctl(struct tty_struct *tty, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case TIOCSPTLCK: /* Set PT Lock (disallow slave open) */
|
||||
return pty_set_lock(tty, (int __user *)arg);
|
||||
case TIOCGPTN: /* Get PT Number */
|
||||
return put_user(tty->index, (unsigned int __user *)arg);
|
||||
case TIOCSIG: /* Send signal to other side of pty */
|
||||
return pty_signal(tty, (int) arg);
|
||||
}
|
||||
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
/**
|
||||
* ptm_unix98_lookup - find a pty master
|
||||
* @driver: ptm driver
|
||||
* @idx: tty index
|
||||
*
|
||||
* Look up a pty master device. Called under the tty_mutex for now.
|
||||
* This provides our locking.
|
||||
*/
|
||||
|
||||
static struct tty_struct *ptm_unix98_lookup(struct tty_driver *driver,
|
||||
struct inode *ptm_inode, int idx)
|
||||
{
|
||||
struct tty_struct *tty = devpts_get_tty(ptm_inode, idx);
|
||||
if (tty)
|
||||
tty = tty->link;
|
||||
return tty;
|
||||
}
|
||||
|
||||
/**
|
||||
* pts_unix98_lookup - find a pty slave
|
||||
* @driver: pts driver
|
||||
* @idx: tty index
|
||||
*
|
||||
* Look up a pty master device. Called under the tty_mutex for now.
|
||||
* This provides our locking.
|
||||
*/
|
||||
|
||||
static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
|
||||
struct inode *pts_inode, int idx)
|
||||
{
|
||||
struct tty_struct *tty = devpts_get_tty(pts_inode, idx);
|
||||
/* Master must be open before slave */
|
||||
if (!tty)
|
||||
return ERR_PTR(-EIO);
|
||||
return tty;
|
||||
}
|
||||
|
||||
static void pty_unix98_shutdown(struct tty_struct *tty)
|
||||
{
|
||||
/* We have our own method as we don't use the tty index */
|
||||
kfree(tty->termios);
|
||||
}
|
||||
|
||||
/* We have no need to install and remove our tty objects as devpts does all
|
||||
the work for us */
|
||||
|
||||
static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty)
|
||||
{
|
||||
struct tty_struct *o_tty;
|
||||
int idx = tty->index;
|
||||
|
||||
o_tty = alloc_tty_struct();
|
||||
if (!o_tty)
|
||||
return -ENOMEM;
|
||||
if (!try_module_get(driver->other->owner)) {
|
||||
/* This cannot in fact currently happen */
|
||||
free_tty_struct(o_tty);
|
||||
return -ENOMEM;
|
||||
}
|
||||
initialize_tty_struct(o_tty, driver->other, idx);
|
||||
|
||||
tty->termios = kzalloc(sizeof(struct ktermios[2]), GFP_KERNEL);
|
||||
if (tty->termios == NULL)
|
||||
goto free_mem_out;
|
||||
*tty->termios = driver->init_termios;
|
||||
tty->termios_locked = tty->termios + 1;
|
||||
|
||||
o_tty->termios = kzalloc(sizeof(struct ktermios[2]), GFP_KERNEL);
|
||||
if (o_tty->termios == NULL)
|
||||
goto free_mem_out;
|
||||
*o_tty->termios = driver->other->init_termios;
|
||||
o_tty->termios_locked = o_tty->termios + 1;
|
||||
|
||||
tty_driver_kref_get(driver->other);
|
||||
if (driver->subtype == PTY_TYPE_MASTER)
|
||||
o_tty->count++;
|
||||
/* Establish the links in both directions */
|
||||
tty->link = o_tty;
|
||||
o_tty->link = tty;
|
||||
/*
|
||||
* All structures have been allocated, so now we install them.
|
||||
* Failures after this point use release_tty to clean up, so
|
||||
* there's no need to null out the local pointers.
|
||||
*/
|
||||
tty_driver_kref_get(driver);
|
||||
tty->count++;
|
||||
pty_count++;
|
||||
return 0;
|
||||
free_mem_out:
|
||||
kfree(o_tty->termios);
|
||||
module_put(o_tty->driver->owner);
|
||||
free_tty_struct(o_tty);
|
||||
kfree(tty->termios);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void pty_unix98_remove(struct tty_driver *driver, struct tty_struct *tty)
|
||||
{
|
||||
pty_count--;
|
||||
}
|
||||
|
||||
static const struct tty_operations ptm_unix98_ops = {
|
||||
.lookup = ptm_unix98_lookup,
|
||||
.install = pty_unix98_install,
|
||||
.remove = pty_unix98_remove,
|
||||
.open = pty_open,
|
||||
.close = pty_close,
|
||||
.write = pty_write,
|
||||
.write_room = pty_write_room,
|
||||
.flush_buffer = pty_flush_buffer,
|
||||
.chars_in_buffer = pty_chars_in_buffer,
|
||||
.unthrottle = pty_unthrottle,
|
||||
.set_termios = pty_set_termios,
|
||||
.ioctl = pty_unix98_ioctl,
|
||||
.shutdown = pty_unix98_shutdown,
|
||||
.resize = pty_resize
|
||||
};
|
||||
|
||||
static const struct tty_operations pty_unix98_ops = {
|
||||
.lookup = pts_unix98_lookup,
|
||||
.install = pty_unix98_install,
|
||||
.remove = pty_unix98_remove,
|
||||
.open = pty_open,
|
||||
.close = pty_close,
|
||||
.write = pty_write,
|
||||
.write_room = pty_write_room,
|
||||
.flush_buffer = pty_flush_buffer,
|
||||
.chars_in_buffer = pty_chars_in_buffer,
|
||||
.unthrottle = pty_unthrottle,
|
||||
.set_termios = pty_set_termios,
|
||||
.shutdown = pty_unix98_shutdown
|
||||
};
|
||||
|
||||
/**
|
||||
* ptmx_open - open a unix 98 pty master
|
||||
* @inode: inode of device file
|
||||
* @filp: file pointer to tty
|
||||
*
|
||||
* Allocate a unix98 pty master device from the ptmx driver.
|
||||
*
|
||||
* Locking: tty_mutex protects the init_dev work. tty->count should
|
||||
* protect the rest.
|
||||
* allocated_ptys_lock handles the list of free pty numbers
|
||||
*/
|
||||
|
||||
static int ptmx_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct tty_struct *tty;
|
||||
int retval;
|
||||
int index;
|
||||
|
||||
nonseekable_open(inode, filp);
|
||||
|
||||
/* find a device that is not in use. */
|
||||
tty_lock();
|
||||
index = devpts_new_index(inode);
|
||||
tty_unlock();
|
||||
if (index < 0)
|
||||
return index;
|
||||
|
||||
mutex_lock(&tty_mutex);
|
||||
tty_lock();
|
||||
tty = tty_init_dev(ptm_driver, index, 1);
|
||||
mutex_unlock(&tty_mutex);
|
||||
|
||||
if (IS_ERR(tty)) {
|
||||
retval = PTR_ERR(tty);
|
||||
goto out;
|
||||
}
|
||||
|
||||
set_bit(TTY_PTY_LOCK, &tty->flags); /* LOCK THE SLAVE */
|
||||
|
||||
retval = tty_add_file(tty, filp);
|
||||
if (retval)
|
||||
goto out;
|
||||
|
||||
retval = devpts_pty_new(inode, tty->link);
|
||||
if (retval)
|
||||
goto out1;
|
||||
|
||||
retval = ptm_driver->ops->open(tty, filp);
|
||||
if (retval)
|
||||
goto out2;
|
||||
out1:
|
||||
tty_unlock();
|
||||
return retval;
|
||||
out2:
|
||||
tty_unlock();
|
||||
tty_release(inode, filp);
|
||||
return retval;
|
||||
out:
|
||||
devpts_kill_index(inode, index);
|
||||
tty_unlock();
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct file_operations ptmx_fops;
|
||||
|
||||
static void __init unix98_pty_init(void)
|
||||
{
|
||||
ptm_driver = alloc_tty_driver(NR_UNIX98_PTY_MAX);
|
||||
if (!ptm_driver)
|
||||
panic("Couldn't allocate Unix98 ptm driver");
|
||||
pts_driver = alloc_tty_driver(NR_UNIX98_PTY_MAX);
|
||||
if (!pts_driver)
|
||||
panic("Couldn't allocate Unix98 pts driver");
|
||||
|
||||
ptm_driver->owner = THIS_MODULE;
|
||||
ptm_driver->driver_name = "pty_master";
|
||||
ptm_driver->name = "ptm";
|
||||
ptm_driver->major = UNIX98_PTY_MASTER_MAJOR;
|
||||
ptm_driver->minor_start = 0;
|
||||
ptm_driver->type = TTY_DRIVER_TYPE_PTY;
|
||||
ptm_driver->subtype = PTY_TYPE_MASTER;
|
||||
ptm_driver->init_termios = tty_std_termios;
|
||||
ptm_driver->init_termios.c_iflag = 0;
|
||||
ptm_driver->init_termios.c_oflag = 0;
|
||||
ptm_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
|
||||
ptm_driver->init_termios.c_lflag = 0;
|
||||
ptm_driver->init_termios.c_ispeed = 38400;
|
||||
ptm_driver->init_termios.c_ospeed = 38400;
|
||||
ptm_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW |
|
||||
TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM;
|
||||
ptm_driver->other = pts_driver;
|
||||
tty_set_operations(ptm_driver, &ptm_unix98_ops);
|
||||
|
||||
pts_driver->owner = THIS_MODULE;
|
||||
pts_driver->driver_name = "pty_slave";
|
||||
pts_driver->name = "pts";
|
||||
pts_driver->major = UNIX98_PTY_SLAVE_MAJOR;
|
||||
pts_driver->minor_start = 0;
|
||||
pts_driver->type = TTY_DRIVER_TYPE_PTY;
|
||||
pts_driver->subtype = PTY_TYPE_SLAVE;
|
||||
pts_driver->init_termios = tty_std_termios;
|
||||
pts_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
|
||||
pts_driver->init_termios.c_ispeed = 38400;
|
||||
pts_driver->init_termios.c_ospeed = 38400;
|
||||
pts_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW |
|
||||
TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM;
|
||||
pts_driver->other = ptm_driver;
|
||||
tty_set_operations(pts_driver, &pty_unix98_ops);
|
||||
|
||||
if (tty_register_driver(ptm_driver))
|
||||
panic("Couldn't register Unix98 ptm driver");
|
||||
if (tty_register_driver(pts_driver))
|
||||
panic("Couldn't register Unix98 pts driver");
|
||||
|
||||
register_sysctl_table(pty_root_table);
|
||||
|
||||
/* Now create the /dev/ptmx special device */
|
||||
tty_default_fops(&ptmx_fops);
|
||||
ptmx_fops.open = ptmx_open;
|
||||
|
||||
cdev_init(&ptmx_cdev, &ptmx_fops);
|
||||
if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) ||
|
||||
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0)
|
||||
panic("Couldn't register /dev/ptmx driver\n");
|
||||
device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
|
||||
}
|
||||
|
||||
#else
|
||||
static inline void unix98_pty_init(void) { }
|
||||
#endif
|
||||
|
||||
static int __init pty_init(void)
|
||||
{
|
||||
legacy_pty_init();
|
||||
unix98_pty_init();
|
||||
return 0;
|
||||
}
|
||||
module_init(pty_init);
|
811
drivers/tty/sysrq.c
Normal file
811
drivers/tty/sysrq.c
Normal file
@@ -0,0 +1,811 @@
|
||||
/*
|
||||
* Linux Magic System Request Key Hacks
|
||||
*
|
||||
* (c) 1997 Martin Mares <mj@atrey.karlin.mff.cuni.cz>
|
||||
* based on ideas by Pavel Machek <pavel@atrey.karlin.mff.cuni.cz>
|
||||
*
|
||||
* (c) 2000 Crutcher Dunnavant <crutcher+kernel@datastacks.com>
|
||||
* overhauled to use key registration
|
||||
* based upon discusions in irc://irc.openprojects.net/#kernelnewbies
|
||||
*
|
||||
* Copyright (c) 2010 Dmitry Torokhov
|
||||
* Input handler conversion
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/major.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/sysrq.h>
|
||||
#include <linux/kbd_kern.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/nmi.h>
|
||||
#include <linux/quotaops.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/buffer_head.h> /* for fsync_bdev() */
|
||||
#include <linux/swap.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/vt_kern.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/oom.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/irq_regs.h>
|
||||
|
||||
/* Whether we react on sysrq keys or just ignore them */
|
||||
static int __read_mostly sysrq_enabled = 1;
|
||||
static bool __read_mostly sysrq_always_enabled;
|
||||
|
||||
static bool sysrq_on(void)
|
||||
{
|
||||
return sysrq_enabled || sysrq_always_enabled;
|
||||
}
|
||||
|
||||
/*
|
||||
* A value of 1 means 'all', other nonzero values are an op mask:
|
||||
*/
|
||||
static bool sysrq_on_mask(int mask)
|
||||
{
|
||||
return sysrq_always_enabled ||
|
||||
sysrq_enabled == 1 ||
|
||||
(sysrq_enabled & mask);
|
||||
}
|
||||
|
||||
static int __init sysrq_always_enabled_setup(char *str)
|
||||
{
|
||||
sysrq_always_enabled = true;
|
||||
pr_info("sysrq always enabled.\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
__setup("sysrq_always_enabled", sysrq_always_enabled_setup);
|
||||
|
||||
|
||||
static void sysrq_handle_loglevel(int key)
|
||||
{
|
||||
int i;
|
||||
|
||||
i = key - '0';
|
||||
console_loglevel = 7;
|
||||
printk("Loglevel set to %d\n", i);
|
||||
console_loglevel = i;
|
||||
}
|
||||
static struct sysrq_key_op sysrq_loglevel_op = {
|
||||
.handler = sysrq_handle_loglevel,
|
||||
.help_msg = "loglevel(0-9)",
|
||||
.action_msg = "Changing Loglevel",
|
||||
.enable_mask = SYSRQ_ENABLE_LOG,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_VT
|
||||
static void sysrq_handle_SAK(int key)
|
||||
{
|
||||
struct work_struct *SAK_work = &vc_cons[fg_console].SAK_work;
|
||||
schedule_work(SAK_work);
|
||||
}
|
||||
static struct sysrq_key_op sysrq_SAK_op = {
|
||||
.handler = sysrq_handle_SAK,
|
||||
.help_msg = "saK",
|
||||
.action_msg = "SAK",
|
||||
.enable_mask = SYSRQ_ENABLE_KEYBOARD,
|
||||
};
|
||||
#else
|
||||
#define sysrq_SAK_op (*(struct sysrq_key_op *)NULL)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_VT
|
||||
static void sysrq_handle_unraw(int key)
|
||||
{
|
||||
struct kbd_struct *kbd = &kbd_table[fg_console];
|
||||
|
||||
if (kbd)
|
||||
kbd->kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;
|
||||
}
|
||||
static struct sysrq_key_op sysrq_unraw_op = {
|
||||
.handler = sysrq_handle_unraw,
|
||||
.help_msg = "unRaw",
|
||||
.action_msg = "Keyboard mode set to system default",
|
||||
.enable_mask = SYSRQ_ENABLE_KEYBOARD,
|
||||
};
|
||||
#else
|
||||
#define sysrq_unraw_op (*(struct sysrq_key_op *)NULL)
|
||||
#endif /* CONFIG_VT */
|
||||
|
||||
static void sysrq_handle_crash(int key)
|
||||
{
|
||||
char *killer = NULL;
|
||||
|
||||
panic_on_oops = 1; /* force panic */
|
||||
wmb();
|
||||
*killer = 1;
|
||||
}
|
||||
static struct sysrq_key_op sysrq_crash_op = {
|
||||
.handler = sysrq_handle_crash,
|
||||
.help_msg = "Crash",
|
||||
.action_msg = "Trigger a crash",
|
||||
.enable_mask = SYSRQ_ENABLE_DUMP,
|
||||
};
|
||||
|
||||
static void sysrq_handle_reboot(int key)
|
||||
{
|
||||
lockdep_off();
|
||||
local_irq_enable();
|
||||
emergency_restart();
|
||||
}
|
||||
static struct sysrq_key_op sysrq_reboot_op = {
|
||||
.handler = sysrq_handle_reboot,
|
||||
.help_msg = "reBoot",
|
||||
.action_msg = "Resetting",
|
||||
.enable_mask = SYSRQ_ENABLE_BOOT,
|
||||
};
|
||||
|
||||
static void sysrq_handle_sync(int key)
|
||||
{
|
||||
emergency_sync();
|
||||
}
|
||||
static struct sysrq_key_op sysrq_sync_op = {
|
||||
.handler = sysrq_handle_sync,
|
||||
.help_msg = "Sync",
|
||||
.action_msg = "Emergency Sync",
|
||||
.enable_mask = SYSRQ_ENABLE_SYNC,
|
||||
};
|
||||
|
||||
static void sysrq_handle_show_timers(int key)
|
||||
{
|
||||
sysrq_timer_list_show();
|
||||
}
|
||||
|
||||
static struct sysrq_key_op sysrq_show_timers_op = {
|
||||
.handler = sysrq_handle_show_timers,
|
||||
.help_msg = "show-all-timers(Q)",
|
||||
.action_msg = "Show clockevent devices & pending hrtimers (no others)",
|
||||
};
|
||||
|
||||
static void sysrq_handle_mountro(int key)
|
||||
{
|
||||
emergency_remount();
|
||||
}
|
||||
static struct sysrq_key_op sysrq_mountro_op = {
|
||||
.handler = sysrq_handle_mountro,
|
||||
.help_msg = "Unmount",
|
||||
.action_msg = "Emergency Remount R/O",
|
||||
.enable_mask = SYSRQ_ENABLE_REMOUNT,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_LOCKDEP
|
||||
static void sysrq_handle_showlocks(int key)
|
||||
{
|
||||
debug_show_all_locks();
|
||||
}
|
||||
|
||||
static struct sysrq_key_op sysrq_showlocks_op = {
|
||||
.handler = sysrq_handle_showlocks,
|
||||
.help_msg = "show-all-locks(D)",
|
||||
.action_msg = "Show Locks Held",
|
||||
};
|
||||
#else
|
||||
#define sysrq_showlocks_op (*(struct sysrq_key_op *)NULL)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
static DEFINE_SPINLOCK(show_lock);
|
||||
|
||||
static void showacpu(void *dummy)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/* Idle CPUs have no interesting backtrace. */
|
||||
if (idle_cpu(smp_processor_id()))
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&show_lock, flags);
|
||||
printk(KERN_INFO "CPU%d:\n", smp_processor_id());
|
||||
show_stack(NULL, NULL);
|
||||
spin_unlock_irqrestore(&show_lock, flags);
|
||||
}
|
||||
|
||||
static void sysrq_showregs_othercpus(struct work_struct *dummy)
|
||||
{
|
||||
smp_call_function(showacpu, NULL, 0);
|
||||
}
|
||||
|
||||
static DECLARE_WORK(sysrq_showallcpus, sysrq_showregs_othercpus);
|
||||
|
||||
static void sysrq_handle_showallcpus(int key)
|
||||
{
|
||||
/*
|
||||
* Fall back to the workqueue based printing if the
|
||||
* backtrace printing did not succeed or the
|
||||
* architecture has no support for it:
|
||||
*/
|
||||
if (!trigger_all_cpu_backtrace()) {
|
||||
struct pt_regs *regs = get_irq_regs();
|
||||
|
||||
if (regs) {
|
||||
printk(KERN_INFO "CPU%d:\n", smp_processor_id());
|
||||
show_regs(regs);
|
||||
}
|
||||
schedule_work(&sysrq_showallcpus);
|
||||
}
|
||||
}
|
||||
|
||||
static struct sysrq_key_op sysrq_showallcpus_op = {
|
||||
.handler = sysrq_handle_showallcpus,
|
||||
.help_msg = "show-backtrace-all-active-cpus(L)",
|
||||
.action_msg = "Show backtrace of all active CPUs",
|
||||
.enable_mask = SYSRQ_ENABLE_DUMP,
|
||||
};
|
||||
#endif
|
||||
|
||||
static void sysrq_handle_showregs(int key)
|
||||
{
|
||||
struct pt_regs *regs = get_irq_regs();
|
||||
if (regs)
|
||||
show_regs(regs);
|
||||
perf_event_print_debug();
|
||||
}
|
||||
static struct sysrq_key_op sysrq_showregs_op = {
|
||||
.handler = sysrq_handle_showregs,
|
||||
.help_msg = "show-registers(P)",
|
||||
.action_msg = "Show Regs",
|
||||
.enable_mask = SYSRQ_ENABLE_DUMP,
|
||||
};
|
||||
|
||||
static void sysrq_handle_showstate(int key)
|
||||
{
|
||||
show_state();
|
||||
}
|
||||
static struct sysrq_key_op sysrq_showstate_op = {
|
||||
.handler = sysrq_handle_showstate,
|
||||
.help_msg = "show-task-states(T)",
|
||||
.action_msg = "Show State",
|
||||
.enable_mask = SYSRQ_ENABLE_DUMP,
|
||||
};
|
||||
|
||||
static void sysrq_handle_showstate_blocked(int key)
|
||||
{
|
||||
show_state_filter(TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
static struct sysrq_key_op sysrq_showstate_blocked_op = {
|
||||
.handler = sysrq_handle_showstate_blocked,
|
||||
.help_msg = "show-blocked-tasks(W)",
|
||||
.action_msg = "Show Blocked State",
|
||||
.enable_mask = SYSRQ_ENABLE_DUMP,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_TRACING
|
||||
#include <linux/ftrace.h>
|
||||
|
||||
static void sysrq_ftrace_dump(int key)
|
||||
{
|
||||
ftrace_dump(DUMP_ALL);
|
||||
}
|
||||
static struct sysrq_key_op sysrq_ftrace_dump_op = {
|
||||
.handler = sysrq_ftrace_dump,
|
||||
.help_msg = "dump-ftrace-buffer(Z)",
|
||||
.action_msg = "Dump ftrace buffer",
|
||||
.enable_mask = SYSRQ_ENABLE_DUMP,
|
||||
};
|
||||
#else
|
||||
#define sysrq_ftrace_dump_op (*(struct sysrq_key_op *)NULL)
|
||||
#endif
|
||||
|
||||
static void sysrq_handle_showmem(int key)
|
||||
{
|
||||
show_mem();
|
||||
}
|
||||
static struct sysrq_key_op sysrq_showmem_op = {
|
||||
.handler = sysrq_handle_showmem,
|
||||
.help_msg = "show-memory-usage(M)",
|
||||
.action_msg = "Show Memory",
|
||||
.enable_mask = SYSRQ_ENABLE_DUMP,
|
||||
};
|
||||
|
||||
/*
|
||||
* Signal sysrq helper function. Sends a signal to all user processes.
|
||||
*/
|
||||
static void send_sig_all(int sig)
|
||||
{
|
||||
struct task_struct *p;
|
||||
|
||||
for_each_process(p) {
|
||||
if (p->mm && !is_global_init(p))
|
||||
/* Not swapper, init nor kernel thread */
|
||||
force_sig(sig, p);
|
||||
}
|
||||
}
|
||||
|
||||
static void sysrq_handle_term(int key)
|
||||
{
|
||||
send_sig_all(SIGTERM);
|
||||
console_loglevel = 8;
|
||||
}
|
||||
static struct sysrq_key_op sysrq_term_op = {
|
||||
.handler = sysrq_handle_term,
|
||||
.help_msg = "terminate-all-tasks(E)",
|
||||
.action_msg = "Terminate All Tasks",
|
||||
.enable_mask = SYSRQ_ENABLE_SIGNAL,
|
||||
};
|
||||
|
||||
static void moom_callback(struct work_struct *ignored)
|
||||
{
|
||||
out_of_memory(node_zonelist(0, GFP_KERNEL), GFP_KERNEL, 0, NULL);
|
||||
}
|
||||
|
||||
static DECLARE_WORK(moom_work, moom_callback);
|
||||
|
||||
static void sysrq_handle_moom(int key)
|
||||
{
|
||||
schedule_work(&moom_work);
|
||||
}
|
||||
static struct sysrq_key_op sysrq_moom_op = {
|
||||
.handler = sysrq_handle_moom,
|
||||
.help_msg = "memory-full-oom-kill(F)",
|
||||
.action_msg = "Manual OOM execution",
|
||||
.enable_mask = SYSRQ_ENABLE_SIGNAL,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_BLOCK
|
||||
static void sysrq_handle_thaw(int key)
|
||||
{
|
||||
emergency_thaw_all();
|
||||
}
|
||||
static struct sysrq_key_op sysrq_thaw_op = {
|
||||
.handler = sysrq_handle_thaw,
|
||||
.help_msg = "thaw-filesystems(J)",
|
||||
.action_msg = "Emergency Thaw of all frozen filesystems",
|
||||
.enable_mask = SYSRQ_ENABLE_SIGNAL,
|
||||
};
|
||||
#endif
|
||||
|
||||
static void sysrq_handle_kill(int key)
|
||||
{
|
||||
send_sig_all(SIGKILL);
|
||||
console_loglevel = 8;
|
||||
}
|
||||
static struct sysrq_key_op sysrq_kill_op = {
|
||||
.handler = sysrq_handle_kill,
|
||||
.help_msg = "kill-all-tasks(I)",
|
||||
.action_msg = "Kill All Tasks",
|
||||
.enable_mask = SYSRQ_ENABLE_SIGNAL,
|
||||
};
|
||||
|
||||
static void sysrq_handle_unrt(int key)
|
||||
{
|
||||
normalize_rt_tasks();
|
||||
}
|
||||
static struct sysrq_key_op sysrq_unrt_op = {
|
||||
.handler = sysrq_handle_unrt,
|
||||
.help_msg = "nice-all-RT-tasks(N)",
|
||||
.action_msg = "Nice All RT Tasks",
|
||||
.enable_mask = SYSRQ_ENABLE_RTNICE,
|
||||
};
|
||||
|
||||
/* Key Operations table and lock */
|
||||
static DEFINE_SPINLOCK(sysrq_key_table_lock);
|
||||
|
||||
static struct sysrq_key_op *sysrq_key_table[36] = {
|
||||
&sysrq_loglevel_op, /* 0 */
|
||||
&sysrq_loglevel_op, /* 1 */
|
||||
&sysrq_loglevel_op, /* 2 */
|
||||
&sysrq_loglevel_op, /* 3 */
|
||||
&sysrq_loglevel_op, /* 4 */
|
||||
&sysrq_loglevel_op, /* 5 */
|
||||
&sysrq_loglevel_op, /* 6 */
|
||||
&sysrq_loglevel_op, /* 7 */
|
||||
&sysrq_loglevel_op, /* 8 */
|
||||
&sysrq_loglevel_op, /* 9 */
|
||||
|
||||
/*
|
||||
* a: Don't use for system provided sysrqs, it is handled specially on
|
||||
* sparc and will never arrive.
|
||||
*/
|
||||
NULL, /* a */
|
||||
&sysrq_reboot_op, /* b */
|
||||
&sysrq_crash_op, /* c & ibm_emac driver debug */
|
||||
&sysrq_showlocks_op, /* d */
|
||||
&sysrq_term_op, /* e */
|
||||
&sysrq_moom_op, /* f */
|
||||
/* g: May be registered for the kernel debugger */
|
||||
NULL, /* g */
|
||||
NULL, /* h - reserved for help */
|
||||
&sysrq_kill_op, /* i */
|
||||
#ifdef CONFIG_BLOCK
|
||||
&sysrq_thaw_op, /* j */
|
||||
#else
|
||||
NULL, /* j */
|
||||
#endif
|
||||
&sysrq_SAK_op, /* k */
|
||||
#ifdef CONFIG_SMP
|
||||
&sysrq_showallcpus_op, /* l */
|
||||
#else
|
||||
NULL, /* l */
|
||||
#endif
|
||||
&sysrq_showmem_op, /* m */
|
||||
&sysrq_unrt_op, /* n */
|
||||
/* o: This will often be registered as 'Off' at init time */
|
||||
NULL, /* o */
|
||||
&sysrq_showregs_op, /* p */
|
||||
&sysrq_show_timers_op, /* q */
|
||||
&sysrq_unraw_op, /* r */
|
||||
&sysrq_sync_op, /* s */
|
||||
&sysrq_showstate_op, /* t */
|
||||
&sysrq_mountro_op, /* u */
|
||||
/* v: May be registered for frame buffer console restore */
|
||||
NULL, /* v */
|
||||
&sysrq_showstate_blocked_op, /* w */
|
||||
/* x: May be registered on ppc/powerpc for xmon */
|
||||
NULL, /* x */
|
||||
/* y: May be registered on sparc64 for global register dump */
|
||||
NULL, /* y */
|
||||
&sysrq_ftrace_dump_op, /* z */
|
||||
};
|
||||
|
||||
/* key2index calculation, -1 on invalid index */
|
||||
static int sysrq_key_table_key2index(int key)
|
||||
{
|
||||
int retval;
|
||||
|
||||
if ((key >= '0') && (key <= '9'))
|
||||
retval = key - '0';
|
||||
else if ((key >= 'a') && (key <= 'z'))
|
||||
retval = key + 10 - 'a';
|
||||
else
|
||||
retval = -1;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* get and put functions for the table, exposed to modules.
|
||||
*/
|
||||
struct sysrq_key_op *__sysrq_get_key_op(int key)
|
||||
{
|
||||
struct sysrq_key_op *op_p = NULL;
|
||||
int i;
|
||||
|
||||
i = sysrq_key_table_key2index(key);
|
||||
if (i != -1)
|
||||
op_p = sysrq_key_table[i];
|
||||
|
||||
return op_p;
|
||||
}
|
||||
|
||||
static void __sysrq_put_key_op(int key, struct sysrq_key_op *op_p)
|
||||
{
|
||||
int i = sysrq_key_table_key2index(key);
|
||||
|
||||
if (i != -1)
|
||||
sysrq_key_table[i] = op_p;
|
||||
}
|
||||
|
||||
void __handle_sysrq(int key, bool check_mask)
|
||||
{
|
||||
struct sysrq_key_op *op_p;
|
||||
int orig_log_level;
|
||||
int i;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&sysrq_key_table_lock, flags);
|
||||
/*
|
||||
* Raise the apparent loglevel to maximum so that the sysrq header
|
||||
* is shown to provide the user with positive feedback. We do not
|
||||
* simply emit this at KERN_EMERG as that would change message
|
||||
* routing in the consumers of /proc/kmsg.
|
||||
*/
|
||||
orig_log_level = console_loglevel;
|
||||
console_loglevel = 7;
|
||||
printk(KERN_INFO "SysRq : ");
|
||||
|
||||
op_p = __sysrq_get_key_op(key);
|
||||
if (op_p) {
|
||||
/*
|
||||
* Should we check for enabled operations (/proc/sysrq-trigger
|
||||
* should not) and is the invoked operation enabled?
|
||||
*/
|
||||
if (!check_mask || sysrq_on_mask(op_p->enable_mask)) {
|
||||
printk("%s\n", op_p->action_msg);
|
||||
console_loglevel = orig_log_level;
|
||||
op_p->handler(key);
|
||||
} else {
|
||||
printk("This sysrq operation is disabled.\n");
|
||||
}
|
||||
} else {
|
||||
printk("HELP : ");
|
||||
/* Only print the help msg once per handler */
|
||||
for (i = 0; i < ARRAY_SIZE(sysrq_key_table); i++) {
|
||||
if (sysrq_key_table[i]) {
|
||||
int j;
|
||||
|
||||
for (j = 0; sysrq_key_table[i] !=
|
||||
sysrq_key_table[j]; j++)
|
||||
;
|
||||
if (j != i)
|
||||
continue;
|
||||
printk("%s ", sysrq_key_table[i]->help_msg);
|
||||
}
|
||||
}
|
||||
printk("\n");
|
||||
console_loglevel = orig_log_level;
|
||||
}
|
||||
spin_unlock_irqrestore(&sysrq_key_table_lock, flags);
|
||||
}
|
||||
|
||||
void handle_sysrq(int key)
|
||||
{
|
||||
if (sysrq_on())
|
||||
__handle_sysrq(key, true);
|
||||
}
|
||||
EXPORT_SYMBOL(handle_sysrq);
|
||||
|
||||
#ifdef CONFIG_INPUT
|
||||
|
||||
/* Simple translation table for the SysRq keys */
|
||||
static const unsigned char sysrq_xlate[KEY_MAX + 1] =
|
||||
"\000\0331234567890-=\177\t" /* 0x00 - 0x0f */
|
||||
"qwertyuiop[]\r\000as" /* 0x10 - 0x1f */
|
||||
"dfghjkl;'`\000\\zxcv" /* 0x20 - 0x2f */
|
||||
"bnm,./\000*\000 \000\201\202\203\204\205" /* 0x30 - 0x3f */
|
||||
"\206\207\210\211\212\000\000789-456+1" /* 0x40 - 0x4f */
|
||||
"230\177\000\000\213\214\000\000\000\000\000\000\000\000\000\000" /* 0x50 - 0x5f */
|
||||
"\r\000/"; /* 0x60 - 0x6f */
|
||||
|
||||
static bool sysrq_down;
|
||||
static int sysrq_alt_use;
|
||||
static int sysrq_alt;
|
||||
static DEFINE_SPINLOCK(sysrq_event_lock);
|
||||
|
||||
static bool sysrq_filter(struct input_handle *handle, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
{
|
||||
bool suppress;
|
||||
|
||||
/* We are called with interrupts disabled, just take the lock */
|
||||
spin_lock(&sysrq_event_lock);
|
||||
|
||||
if (type != EV_KEY)
|
||||
goto out;
|
||||
|
||||
switch (code) {
|
||||
|
||||
case KEY_LEFTALT:
|
||||
case KEY_RIGHTALT:
|
||||
if (value)
|
||||
sysrq_alt = code;
|
||||
else {
|
||||
if (sysrq_down && code == sysrq_alt_use)
|
||||
sysrq_down = false;
|
||||
|
||||
sysrq_alt = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_SYSRQ:
|
||||
if (value == 1 && sysrq_alt) {
|
||||
sysrq_down = true;
|
||||
sysrq_alt_use = sysrq_alt;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (sysrq_down && value && value != 2)
|
||||
__handle_sysrq(sysrq_xlate[code], true);
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
suppress = sysrq_down;
|
||||
spin_unlock(&sysrq_event_lock);
|
||||
|
||||
return suppress;
|
||||
}
|
||||
|
||||
static int sysrq_connect(struct input_handler *handler,
|
||||
struct input_dev *dev,
|
||||
const struct input_device_id *id)
|
||||
{
|
||||
struct input_handle *handle;
|
||||
int error;
|
||||
|
||||
sysrq_down = false;
|
||||
sysrq_alt = 0;
|
||||
|
||||
handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
|
||||
if (!handle)
|
||||
return -ENOMEM;
|
||||
|
||||
handle->dev = dev;
|
||||
handle->handler = handler;
|
||||
handle->name = "sysrq";
|
||||
|
||||
error = input_register_handle(handle);
|
||||
if (error) {
|
||||
pr_err("Failed to register input sysrq handler, error %d\n",
|
||||
error);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
error = input_open_device(handle);
|
||||
if (error) {
|
||||
pr_err("Failed to open input device, error %d\n", error);
|
||||
goto err_unregister;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
input_unregister_handle(handle);
|
||||
err_free:
|
||||
kfree(handle);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void sysrq_disconnect(struct input_handle *handle)
|
||||
{
|
||||
input_close_device(handle);
|
||||
input_unregister_handle(handle);
|
||||
kfree(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* We are matching on KEY_LEFTALT instead of KEY_SYSRQ because not all
|
||||
* keyboards have SysRq key predefined and so user may add it to keymap
|
||||
* later, but we expect all such keyboards to have left alt.
|
||||
*/
|
||||
static const struct input_device_id sysrq_ids[] = {
|
||||
{
|
||||
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
|
||||
INPUT_DEVICE_ID_MATCH_KEYBIT,
|
||||
.evbit = { BIT_MASK(EV_KEY) },
|
||||
.keybit = { BIT_MASK(KEY_LEFTALT) },
|
||||
},
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct input_handler sysrq_handler = {
|
||||
.filter = sysrq_filter,
|
||||
.connect = sysrq_connect,
|
||||
.disconnect = sysrq_disconnect,
|
||||
.name = "sysrq",
|
||||
.id_table = sysrq_ids,
|
||||
};
|
||||
|
||||
static bool sysrq_handler_registered;
|
||||
|
||||
static inline void sysrq_register_handler(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = input_register_handler(&sysrq_handler);
|
||||
if (error)
|
||||
pr_err("Failed to register input handler, error %d", error);
|
||||
else
|
||||
sysrq_handler_registered = true;
|
||||
}
|
||||
|
||||
static inline void sysrq_unregister_handler(void)
|
||||
{
|
||||
if (sysrq_handler_registered) {
|
||||
input_unregister_handler(&sysrq_handler);
|
||||
sysrq_handler_registered = false;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline void sysrq_register_handler(void)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void sysrq_unregister_handler(void)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_INPUT */
|
||||
|
||||
int sysrq_toggle_support(int enable_mask)
|
||||
{
|
||||
bool was_enabled = sysrq_on();
|
||||
|
||||
sysrq_enabled = enable_mask;
|
||||
|
||||
if (was_enabled != sysrq_on()) {
|
||||
if (sysrq_on())
|
||||
sysrq_register_handler();
|
||||
else
|
||||
sysrq_unregister_handler();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __sysrq_swap_key_ops(int key, struct sysrq_key_op *insert_op_p,
|
||||
struct sysrq_key_op *remove_op_p)
|
||||
{
|
||||
int retval;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&sysrq_key_table_lock, flags);
|
||||
if (__sysrq_get_key_op(key) == remove_op_p) {
|
||||
__sysrq_put_key_op(key, insert_op_p);
|
||||
retval = 0;
|
||||
} else {
|
||||
retval = -1;
|
||||
}
|
||||
spin_unlock_irqrestore(&sysrq_key_table_lock, flags);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int register_sysrq_key(int key, struct sysrq_key_op *op_p)
|
||||
{
|
||||
return __sysrq_swap_key_ops(key, op_p, NULL);
|
||||
}
|
||||
EXPORT_SYMBOL(register_sysrq_key);
|
||||
|
||||
int unregister_sysrq_key(int key, struct sysrq_key_op *op_p)
|
||||
{
|
||||
return __sysrq_swap_key_ops(key, NULL, op_p);
|
||||
}
|
||||
EXPORT_SYMBOL(unregister_sysrq_key);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/*
|
||||
* writing 'C' to /proc/sysrq-trigger is like sysrq-C
|
||||
*/
|
||||
static ssize_t write_sysrq_trigger(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
if (count) {
|
||||
char c;
|
||||
|
||||
if (get_user(c, buf))
|
||||
return -EFAULT;
|
||||
__handle_sysrq(c, false);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations proc_sysrq_trigger_operations = {
|
||||
.write = write_sysrq_trigger,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
static void sysrq_init_procfs(void)
|
||||
{
|
||||
if (!proc_create("sysrq-trigger", S_IWUSR, NULL,
|
||||
&proc_sysrq_trigger_operations))
|
||||
pr_err("Failed to register proc interface\n");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline void sysrq_init_procfs(void)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
static int __init sysrq_init(void)
|
||||
{
|
||||
sysrq_init_procfs();
|
||||
|
||||
if (sysrq_on())
|
||||
sysrq_register_handler();
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(sysrq_init);
|
358
drivers/tty/tty_audit.c
Normal file
358
drivers/tty/tty_audit.c
Normal file
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Creating audit events from TTY input.
|
||||
*
|
||||
* Copyright (C) 2007 Red Hat, Inc. All rights reserved. This copyrighted
|
||||
* material is made available to anyone wishing to use, modify, copy, or
|
||||
* redistribute it subject to the terms and conditions of the GNU General
|
||||
* Public License v.2.
|
||||
*
|
||||
* Authors: Miloslav Trmac <mitr@redhat.com>
|
||||
*/
|
||||
|
||||
#include <linux/audit.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/tty.h>
|
||||
|
||||
struct tty_audit_buf {
|
||||
atomic_t count;
|
||||
struct mutex mutex; /* Protects all data below */
|
||||
int major, minor; /* The TTY which the data is from */
|
||||
unsigned icanon:1;
|
||||
size_t valid;
|
||||
unsigned char *data; /* Allocated size N_TTY_BUF_SIZE */
|
||||
};
|
||||
|
||||
static struct tty_audit_buf *tty_audit_buf_alloc(int major, int minor,
|
||||
int icanon)
|
||||
{
|
||||
struct tty_audit_buf *buf;
|
||||
|
||||
buf = kmalloc(sizeof(*buf), GFP_KERNEL);
|
||||
if (!buf)
|
||||
goto err;
|
||||
buf->data = kmalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
|
||||
if (!buf->data)
|
||||
goto err_buf;
|
||||
atomic_set(&buf->count, 1);
|
||||
mutex_init(&buf->mutex);
|
||||
buf->major = major;
|
||||
buf->minor = minor;
|
||||
buf->icanon = icanon;
|
||||
buf->valid = 0;
|
||||
return buf;
|
||||
|
||||
err_buf:
|
||||
kfree(buf);
|
||||
err:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void tty_audit_buf_free(struct tty_audit_buf *buf)
|
||||
{
|
||||
WARN_ON(buf->valid != 0);
|
||||
kfree(buf->data);
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
static void tty_audit_buf_put(struct tty_audit_buf *buf)
|
||||
{
|
||||
if (atomic_dec_and_test(&buf->count))
|
||||
tty_audit_buf_free(buf);
|
||||
}
|
||||
|
||||
static void tty_audit_log(const char *description, struct task_struct *tsk,
|
||||
uid_t loginuid, unsigned sessionid, int major,
|
||||
int minor, unsigned char *data, size_t size)
|
||||
{
|
||||
struct audit_buffer *ab;
|
||||
|
||||
ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_TTY);
|
||||
if (ab) {
|
||||
char name[sizeof(tsk->comm)];
|
||||
uid_t uid = task_uid(tsk);
|
||||
|
||||
audit_log_format(ab, "%s pid=%u uid=%u auid=%u ses=%u "
|
||||
"major=%d minor=%d comm=", description,
|
||||
tsk->pid, uid, loginuid, sessionid,
|
||||
major, minor);
|
||||
get_task_comm(name, tsk);
|
||||
audit_log_untrustedstring(ab, name);
|
||||
audit_log_format(ab, " data=");
|
||||
audit_log_n_hex(ab, data, size);
|
||||
audit_log_end(ab);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_audit_buf_push - Push buffered data out
|
||||
*
|
||||
* Generate an audit message from the contents of @buf, which is owned by
|
||||
* @tsk with @loginuid. @buf->mutex must be locked.
|
||||
*/
|
||||
static void tty_audit_buf_push(struct task_struct *tsk, uid_t loginuid,
|
||||
unsigned int sessionid,
|
||||
struct tty_audit_buf *buf)
|
||||
{
|
||||
if (buf->valid == 0)
|
||||
return;
|
||||
if (audit_enabled == 0)
|
||||
return;
|
||||
tty_audit_log("tty", tsk, loginuid, sessionid, buf->major, buf->minor,
|
||||
buf->data, buf->valid);
|
||||
buf->valid = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_audit_buf_push_current - Push buffered data out
|
||||
*
|
||||
* Generate an audit message from the contents of @buf, which is owned by
|
||||
* the current task. @buf->mutex must be locked.
|
||||
*/
|
||||
static void tty_audit_buf_push_current(struct tty_audit_buf *buf)
|
||||
{
|
||||
uid_t auid = audit_get_loginuid(current);
|
||||
unsigned int sessionid = audit_get_sessionid(current);
|
||||
tty_audit_buf_push(current, auid, sessionid, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_audit_exit - Handle a task exit
|
||||
*
|
||||
* Make sure all buffered data is written out and deallocate the buffer.
|
||||
* Only needs to be called if current->signal->tty_audit_buf != %NULL.
|
||||
*/
|
||||
void tty_audit_exit(void)
|
||||
{
|
||||
struct tty_audit_buf *buf;
|
||||
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
buf = current->signal->tty_audit_buf;
|
||||
current->signal->tty_audit_buf = NULL;
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
mutex_lock(&buf->mutex);
|
||||
tty_audit_buf_push_current(buf);
|
||||
mutex_unlock(&buf->mutex);
|
||||
|
||||
tty_audit_buf_put(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_audit_fork - Copy TTY audit state for a new task
|
||||
*
|
||||
* Set up TTY audit state in @sig from current. @sig needs no locking.
|
||||
*/
|
||||
void tty_audit_fork(struct signal_struct *sig)
|
||||
{
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
sig->audit_tty = current->signal->audit_tty;
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_audit_tiocsti - Log TIOCSTI
|
||||
*/
|
||||
void tty_audit_tiocsti(struct tty_struct *tty, char ch)
|
||||
{
|
||||
struct tty_audit_buf *buf;
|
||||
int major, minor, should_audit;
|
||||
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
should_audit = current->signal->audit_tty;
|
||||
buf = current->signal->tty_audit_buf;
|
||||
if (buf)
|
||||
atomic_inc(&buf->count);
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
|
||||
major = tty->driver->major;
|
||||
minor = tty->driver->minor_start + tty->index;
|
||||
if (buf) {
|
||||
mutex_lock(&buf->mutex);
|
||||
if (buf->major == major && buf->minor == minor)
|
||||
tty_audit_buf_push_current(buf);
|
||||
mutex_unlock(&buf->mutex);
|
||||
tty_audit_buf_put(buf);
|
||||
}
|
||||
|
||||
if (should_audit && audit_enabled) {
|
||||
uid_t auid;
|
||||
unsigned int sessionid;
|
||||
|
||||
auid = audit_get_loginuid(current);
|
||||
sessionid = audit_get_sessionid(current);
|
||||
tty_audit_log("ioctl=TIOCSTI", current, auid, sessionid, major,
|
||||
minor, &ch, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_audit_push_task - Flush task's pending audit data
|
||||
* @tsk: task pointer
|
||||
* @loginuid: sender login uid
|
||||
* @sessionid: sender session id
|
||||
*
|
||||
* Called with a ref on @tsk held. Try to lock sighand and get a
|
||||
* reference to the tty audit buffer if available.
|
||||
* Flush the buffer or return an appropriate error code.
|
||||
*/
|
||||
int tty_audit_push_task(struct task_struct *tsk, uid_t loginuid, u32 sessionid)
|
||||
{
|
||||
struct tty_audit_buf *buf = ERR_PTR(-EPERM);
|
||||
unsigned long flags;
|
||||
|
||||
if (!lock_task_sighand(tsk, &flags))
|
||||
return -ESRCH;
|
||||
|
||||
if (tsk->signal->audit_tty) {
|
||||
buf = tsk->signal->tty_audit_buf;
|
||||
if (buf)
|
||||
atomic_inc(&buf->count);
|
||||
}
|
||||
unlock_task_sighand(tsk, &flags);
|
||||
|
||||
/*
|
||||
* Return 0 when signal->audit_tty set
|
||||
* but tsk->signal->tty_audit_buf == NULL.
|
||||
*/
|
||||
if (!buf || IS_ERR(buf))
|
||||
return PTR_ERR(buf);
|
||||
|
||||
mutex_lock(&buf->mutex);
|
||||
tty_audit_buf_push(tsk, loginuid, sessionid, buf);
|
||||
mutex_unlock(&buf->mutex);
|
||||
|
||||
tty_audit_buf_put(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_audit_buf_get - Get an audit buffer.
|
||||
*
|
||||
* Get an audit buffer for @tty, allocate it if necessary. Return %NULL
|
||||
* if TTY auditing is disabled or out of memory. Otherwise, return a new
|
||||
* reference to the buffer.
|
||||
*/
|
||||
static struct tty_audit_buf *tty_audit_buf_get(struct tty_struct *tty)
|
||||
{
|
||||
struct tty_audit_buf *buf, *buf2;
|
||||
|
||||
buf = NULL;
|
||||
buf2 = NULL;
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
if (likely(!current->signal->audit_tty))
|
||||
goto out;
|
||||
buf = current->signal->tty_audit_buf;
|
||||
if (buf) {
|
||||
atomic_inc(&buf->count);
|
||||
goto out;
|
||||
}
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
|
||||
buf2 = tty_audit_buf_alloc(tty->driver->major,
|
||||
tty->driver->minor_start + tty->index,
|
||||
tty->icanon);
|
||||
if (buf2 == NULL) {
|
||||
audit_log_lost("out of memory in TTY auditing");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
if (!current->signal->audit_tty)
|
||||
goto out;
|
||||
buf = current->signal->tty_audit_buf;
|
||||
if (!buf) {
|
||||
current->signal->tty_audit_buf = buf2;
|
||||
buf = buf2;
|
||||
buf2 = NULL;
|
||||
}
|
||||
atomic_inc(&buf->count);
|
||||
/* Fall through */
|
||||
out:
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
if (buf2)
|
||||
tty_audit_buf_free(buf2);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_audit_add_data - Add data for TTY auditing.
|
||||
*
|
||||
* Audit @data of @size from @tty, if necessary.
|
||||
*/
|
||||
void tty_audit_add_data(struct tty_struct *tty, unsigned char *data,
|
||||
size_t size)
|
||||
{
|
||||
struct tty_audit_buf *buf;
|
||||
int major, minor;
|
||||
|
||||
if (unlikely(size == 0))
|
||||
return;
|
||||
|
||||
if (tty->driver->type == TTY_DRIVER_TYPE_PTY
|
||||
&& tty->driver->subtype == PTY_TYPE_MASTER)
|
||||
return;
|
||||
|
||||
buf = tty_audit_buf_get(tty);
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
mutex_lock(&buf->mutex);
|
||||
major = tty->driver->major;
|
||||
minor = tty->driver->minor_start + tty->index;
|
||||
if (buf->major != major || buf->minor != minor
|
||||
|| buf->icanon != tty->icanon) {
|
||||
tty_audit_buf_push_current(buf);
|
||||
buf->major = major;
|
||||
buf->minor = minor;
|
||||
buf->icanon = tty->icanon;
|
||||
}
|
||||
do {
|
||||
size_t run;
|
||||
|
||||
run = N_TTY_BUF_SIZE - buf->valid;
|
||||
if (run > size)
|
||||
run = size;
|
||||
memcpy(buf->data + buf->valid, data, run);
|
||||
buf->valid += run;
|
||||
data += run;
|
||||
size -= run;
|
||||
if (buf->valid == N_TTY_BUF_SIZE)
|
||||
tty_audit_buf_push_current(buf);
|
||||
} while (size != 0);
|
||||
mutex_unlock(&buf->mutex);
|
||||
tty_audit_buf_put(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_audit_push - Push buffered data out
|
||||
*
|
||||
* Make sure no audit data is pending for @tty on the current process.
|
||||
*/
|
||||
void tty_audit_push(struct tty_struct *tty)
|
||||
{
|
||||
struct tty_audit_buf *buf;
|
||||
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
if (likely(!current->signal->audit_tty)) {
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
return;
|
||||
}
|
||||
buf = current->signal->tty_audit_buf;
|
||||
if (buf)
|
||||
atomic_inc(&buf->count);
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
|
||||
if (buf) {
|
||||
int major, minor;
|
||||
|
||||
major = tty->driver->major;
|
||||
minor = tty->driver->minor_start + tty->index;
|
||||
mutex_lock(&buf->mutex);
|
||||
if (buf->major == major && buf->minor == minor)
|
||||
tty_audit_buf_push_current(buf);
|
||||
mutex_unlock(&buf->mutex);
|
||||
tty_audit_buf_put(buf);
|
||||
}
|
||||
}
|
524
drivers/tty/tty_buffer.c
Normal file
524
drivers/tty/tty_buffer.c
Normal file
@@ -0,0 +1,524 @@
|
||||
/*
|
||||
* Tty buffer allocation management
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_driver.h>
|
||||
#include <linux/tty_flip.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
/**
|
||||
* tty_buffer_free_all - free buffers used by a tty
|
||||
* @tty: tty to free from
|
||||
*
|
||||
* Remove all the buffers pending on a tty whether queued with data
|
||||
* or in the free ring. Must be called when the tty is no longer in use
|
||||
*
|
||||
* Locking: none
|
||||
*/
|
||||
|
||||
void tty_buffer_free_all(struct tty_struct *tty)
|
||||
{
|
||||
struct tty_buffer *thead;
|
||||
while ((thead = tty->buf.head) != NULL) {
|
||||
tty->buf.head = thead->next;
|
||||
kfree(thead);
|
||||
}
|
||||
while ((thead = tty->buf.free) != NULL) {
|
||||
tty->buf.free = thead->next;
|
||||
kfree(thead);
|
||||
}
|
||||
tty->buf.tail = NULL;
|
||||
tty->buf.memory_used = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_buffer_alloc - allocate a tty buffer
|
||||
* @tty: tty device
|
||||
* @size: desired size (characters)
|
||||
*
|
||||
* Allocate a new tty buffer to hold the desired number of characters.
|
||||
* Return NULL if out of memory or the allocation would exceed the
|
||||
* per device queue
|
||||
*
|
||||
* Locking: Caller must hold tty->buf.lock
|
||||
*/
|
||||
|
||||
static struct tty_buffer *tty_buffer_alloc(struct tty_struct *tty, size_t size)
|
||||
{
|
||||
struct tty_buffer *p;
|
||||
|
||||
if (tty->buf.memory_used + size > 65536)
|
||||
return NULL;
|
||||
p = kmalloc(sizeof(struct tty_buffer) + 2 * size, GFP_ATOMIC);
|
||||
if (p == NULL)
|
||||
return NULL;
|
||||
p->used = 0;
|
||||
p->size = size;
|
||||
p->next = NULL;
|
||||
p->commit = 0;
|
||||
p->read = 0;
|
||||
p->char_buf_ptr = (char *)(p->data);
|
||||
p->flag_buf_ptr = (unsigned char *)p->char_buf_ptr + size;
|
||||
tty->buf.memory_used += size;
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_buffer_free - free a tty buffer
|
||||
* @tty: tty owning the buffer
|
||||
* @b: the buffer to free
|
||||
*
|
||||
* Free a tty buffer, or add it to the free list according to our
|
||||
* internal strategy
|
||||
*
|
||||
* Locking: Caller must hold tty->buf.lock
|
||||
*/
|
||||
|
||||
static void tty_buffer_free(struct tty_struct *tty, struct tty_buffer *b)
|
||||
{
|
||||
/* Dumb strategy for now - should keep some stats */
|
||||
tty->buf.memory_used -= b->size;
|
||||
WARN_ON(tty->buf.memory_used < 0);
|
||||
|
||||
if (b->size >= 512)
|
||||
kfree(b);
|
||||
else {
|
||||
b->next = tty->buf.free;
|
||||
tty->buf.free = b;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* __tty_buffer_flush - flush full tty buffers
|
||||
* @tty: tty to flush
|
||||
*
|
||||
* flush all the buffers containing receive data. Caller must
|
||||
* hold the buffer lock and must have ensured no parallel flush to
|
||||
* ldisc is running.
|
||||
*
|
||||
* Locking: Caller must hold tty->buf.lock
|
||||
*/
|
||||
|
||||
static void __tty_buffer_flush(struct tty_struct *tty)
|
||||
{
|
||||
struct tty_buffer *thead;
|
||||
|
||||
while ((thead = tty->buf.head) != NULL) {
|
||||
tty->buf.head = thead->next;
|
||||
tty_buffer_free(tty, thead);
|
||||
}
|
||||
tty->buf.tail = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_buffer_flush - flush full tty buffers
|
||||
* @tty: tty to flush
|
||||
*
|
||||
* flush all the buffers containing receive data. If the buffer is
|
||||
* being processed by flush_to_ldisc then we defer the processing
|
||||
* to that function
|
||||
*
|
||||
* Locking: none
|
||||
*/
|
||||
|
||||
void tty_buffer_flush(struct tty_struct *tty)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tty->buf.lock, flags);
|
||||
|
||||
/* If the data is being pushed to the tty layer then we can't
|
||||
process it here. Instead set a flag and the flush_to_ldisc
|
||||
path will process the flush request before it exits */
|
||||
if (test_bit(TTY_FLUSHING, &tty->flags)) {
|
||||
set_bit(TTY_FLUSHPENDING, &tty->flags);
|
||||
spin_unlock_irqrestore(&tty->buf.lock, flags);
|
||||
wait_event(tty->read_wait,
|
||||
test_bit(TTY_FLUSHPENDING, &tty->flags) == 0);
|
||||
return;
|
||||
} else
|
||||
__tty_buffer_flush(tty);
|
||||
spin_unlock_irqrestore(&tty->buf.lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_buffer_find - find a free tty buffer
|
||||
* @tty: tty owning the buffer
|
||||
* @size: characters wanted
|
||||
*
|
||||
* Locate an existing suitable tty buffer or if we are lacking one then
|
||||
* allocate a new one. We round our buffers off in 256 character chunks
|
||||
* to get better allocation behaviour.
|
||||
*
|
||||
* Locking: Caller must hold tty->buf.lock
|
||||
*/
|
||||
|
||||
static struct tty_buffer *tty_buffer_find(struct tty_struct *tty, size_t size)
|
||||
{
|
||||
struct tty_buffer **tbh = &tty->buf.free;
|
||||
while ((*tbh) != NULL) {
|
||||
struct tty_buffer *t = *tbh;
|
||||
if (t->size >= size) {
|
||||
*tbh = t->next;
|
||||
t->next = NULL;
|
||||
t->used = 0;
|
||||
t->commit = 0;
|
||||
t->read = 0;
|
||||
tty->buf.memory_used += t->size;
|
||||
return t;
|
||||
}
|
||||
tbh = &((*tbh)->next);
|
||||
}
|
||||
/* Round the buffer size out */
|
||||
size = (size + 0xFF) & ~0xFF;
|
||||
return tty_buffer_alloc(tty, size);
|
||||
/* Should possibly check if this fails for the largest buffer we
|
||||
have queued and recycle that ? */
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_buffer_request_room - grow tty buffer if needed
|
||||
* @tty: tty structure
|
||||
* @size: size desired
|
||||
*
|
||||
* Make at least size bytes of linear space available for the tty
|
||||
* buffer. If we fail return the size we managed to find.
|
||||
*
|
||||
* Locking: Takes tty->buf.lock
|
||||
*/
|
||||
int tty_buffer_request_room(struct tty_struct *tty, size_t size)
|
||||
{
|
||||
struct tty_buffer *b, *n;
|
||||
int left;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&tty->buf.lock, flags);
|
||||
|
||||
/* OPTIMISATION: We could keep a per tty "zero" sized buffer to
|
||||
remove this conditional if its worth it. This would be invisible
|
||||
to the callers */
|
||||
if ((b = tty->buf.tail) != NULL)
|
||||
left = b->size - b->used;
|
||||
else
|
||||
left = 0;
|
||||
|
||||
if (left < size) {
|
||||
/* This is the slow path - looking for new buffers to use */
|
||||
if ((n = tty_buffer_find(tty, size)) != NULL) {
|
||||
if (b != NULL) {
|
||||
b->next = n;
|
||||
b->commit = b->used;
|
||||
} else
|
||||
tty->buf.head = n;
|
||||
tty->buf.tail = n;
|
||||
} else
|
||||
size = left;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&tty->buf.lock, flags);
|
||||
return size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tty_buffer_request_room);
|
||||
|
||||
/**
|
||||
* tty_insert_flip_string_fixed_flag - Add characters to the tty buffer
|
||||
* @tty: tty structure
|
||||
* @chars: characters
|
||||
* @flag: flag value for each character
|
||||
* @size: size
|
||||
*
|
||||
* Queue a series of bytes to the tty buffering. All the characters
|
||||
* passed are marked with the supplied flag. Returns the number added.
|
||||
*
|
||||
* Locking: Called functions may take tty->buf.lock
|
||||
*/
|
||||
|
||||
int tty_insert_flip_string_fixed_flag(struct tty_struct *tty,
|
||||
const unsigned char *chars, char flag, size_t size)
|
||||
{
|
||||
int copied = 0;
|
||||
do {
|
||||
int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);
|
||||
int space = tty_buffer_request_room(tty, goal);
|
||||
struct tty_buffer *tb = tty->buf.tail;
|
||||
/* If there is no space then tb may be NULL */
|
||||
if (unlikely(space == 0))
|
||||
break;
|
||||
memcpy(tb->char_buf_ptr + tb->used, chars, space);
|
||||
memset(tb->flag_buf_ptr + tb->used, flag, space);
|
||||
tb->used += space;
|
||||
copied += space;
|
||||
chars += space;
|
||||
/* There is a small chance that we need to split the data over
|
||||
several buffers. If this is the case we must loop */
|
||||
} while (unlikely(size > copied));
|
||||
return copied;
|
||||
}
|
||||
EXPORT_SYMBOL(tty_insert_flip_string_fixed_flag);
|
||||
|
||||
/**
|
||||
* tty_insert_flip_string_flags - Add characters to the tty buffer
|
||||
* @tty: tty structure
|
||||
* @chars: characters
|
||||
* @flags: flag bytes
|
||||
* @size: size
|
||||
*
|
||||
* Queue a series of bytes to the tty buffering. For each character
|
||||
* the flags array indicates the status of the character. Returns the
|
||||
* number added.
|
||||
*
|
||||
* Locking: Called functions may take tty->buf.lock
|
||||
*/
|
||||
|
||||
int tty_insert_flip_string_flags(struct tty_struct *tty,
|
||||
const unsigned char *chars, const char *flags, size_t size)
|
||||
{
|
||||
int copied = 0;
|
||||
do {
|
||||
int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);
|
||||
int space = tty_buffer_request_room(tty, goal);
|
||||
struct tty_buffer *tb = tty->buf.tail;
|
||||
/* If there is no space then tb may be NULL */
|
||||
if (unlikely(space == 0))
|
||||
break;
|
||||
memcpy(tb->char_buf_ptr + tb->used, chars, space);
|
||||
memcpy(tb->flag_buf_ptr + tb->used, flags, space);
|
||||
tb->used += space;
|
||||
copied += space;
|
||||
chars += space;
|
||||
flags += space;
|
||||
/* There is a small chance that we need to split the data over
|
||||
several buffers. If this is the case we must loop */
|
||||
} while (unlikely(size > copied));
|
||||
return copied;
|
||||
}
|
||||
EXPORT_SYMBOL(tty_insert_flip_string_flags);
|
||||
|
||||
/**
|
||||
* tty_schedule_flip - push characters to ldisc
|
||||
* @tty: tty to push from
|
||||
*
|
||||
* Takes any pending buffers and transfers their ownership to the
|
||||
* ldisc side of the queue. It then schedules those characters for
|
||||
* processing by the line discipline.
|
||||
*
|
||||
* Locking: Takes tty->buf.lock
|
||||
*/
|
||||
|
||||
void tty_schedule_flip(struct tty_struct *tty)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tty->buf.lock, flags);
|
||||
if (tty->buf.tail != NULL)
|
||||
tty->buf.tail->commit = tty->buf.tail->used;
|
||||
spin_unlock_irqrestore(&tty->buf.lock, flags);
|
||||
schedule_delayed_work(&tty->buf.work, 1);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_schedule_flip);
|
||||
|
||||
/**
|
||||
* tty_prepare_flip_string - make room for characters
|
||||
* @tty: tty
|
||||
* @chars: return pointer for character write area
|
||||
* @size: desired size
|
||||
*
|
||||
* Prepare a block of space in the buffer for data. Returns the length
|
||||
* available and buffer pointer to the space which is now allocated and
|
||||
* accounted for as ready for normal characters. This is used for drivers
|
||||
* that need their own block copy routines into the buffer. There is no
|
||||
* guarantee the buffer is a DMA target!
|
||||
*
|
||||
* Locking: May call functions taking tty->buf.lock
|
||||
*/
|
||||
|
||||
int tty_prepare_flip_string(struct tty_struct *tty, unsigned char **chars,
|
||||
size_t size)
|
||||
{
|
||||
int space = tty_buffer_request_room(tty, size);
|
||||
if (likely(space)) {
|
||||
struct tty_buffer *tb = tty->buf.tail;
|
||||
*chars = tb->char_buf_ptr + tb->used;
|
||||
memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space);
|
||||
tb->used += space;
|
||||
}
|
||||
return space;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tty_prepare_flip_string);
|
||||
|
||||
/**
|
||||
* tty_prepare_flip_string_flags - make room for characters
|
||||
* @tty: tty
|
||||
* @chars: return pointer for character write area
|
||||
* @flags: return pointer for status flag write area
|
||||
* @size: desired size
|
||||
*
|
||||
* Prepare a block of space in the buffer for data. Returns the length
|
||||
* available and buffer pointer to the space which is now allocated and
|
||||
* accounted for as ready for characters. This is used for drivers
|
||||
* that need their own block copy routines into the buffer. There is no
|
||||
* guarantee the buffer is a DMA target!
|
||||
*
|
||||
* Locking: May call functions taking tty->buf.lock
|
||||
*/
|
||||
|
||||
int tty_prepare_flip_string_flags(struct tty_struct *tty,
|
||||
unsigned char **chars, char **flags, size_t size)
|
||||
{
|
||||
int space = tty_buffer_request_room(tty, size);
|
||||
if (likely(space)) {
|
||||
struct tty_buffer *tb = tty->buf.tail;
|
||||
*chars = tb->char_buf_ptr + tb->used;
|
||||
*flags = tb->flag_buf_ptr + tb->used;
|
||||
tb->used += space;
|
||||
}
|
||||
return space;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tty_prepare_flip_string_flags);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* flush_to_ldisc
|
||||
* @work: tty structure passed from work queue.
|
||||
*
|
||||
* This routine is called out of the software interrupt to flush data
|
||||
* from the buffer chain to the line discipline.
|
||||
*
|
||||
* Locking: holds tty->buf.lock to guard buffer list. Drops the lock
|
||||
* while invoking the line discipline receive_buf method. The
|
||||
* receive_buf method is single threaded for each tty instance.
|
||||
*/
|
||||
|
||||
static void flush_to_ldisc(struct work_struct *work)
|
||||
{
|
||||
struct tty_struct *tty =
|
||||
container_of(work, struct tty_struct, buf.work.work);
|
||||
unsigned long flags;
|
||||
struct tty_ldisc *disc;
|
||||
|
||||
disc = tty_ldisc_ref(tty);
|
||||
if (disc == NULL) /* !TTY_LDISC */
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&tty->buf.lock, flags);
|
||||
|
||||
if (!test_and_set_bit(TTY_FLUSHING, &tty->flags)) {
|
||||
struct tty_buffer *head;
|
||||
while ((head = tty->buf.head) != NULL) {
|
||||
int count;
|
||||
char *char_buf;
|
||||
unsigned char *flag_buf;
|
||||
|
||||
count = head->commit - head->read;
|
||||
if (!count) {
|
||||
if (head->next == NULL)
|
||||
break;
|
||||
tty->buf.head = head->next;
|
||||
tty_buffer_free(tty, head);
|
||||
continue;
|
||||
}
|
||||
/* Ldisc or user is trying to flush the buffers
|
||||
we are feeding to the ldisc, stop feeding the
|
||||
line discipline as we want to empty the queue */
|
||||
if (test_bit(TTY_FLUSHPENDING, &tty->flags))
|
||||
break;
|
||||
if (!tty->receive_room) {
|
||||
schedule_delayed_work(&tty->buf.work, 1);
|
||||
break;
|
||||
}
|
||||
if (count > tty->receive_room)
|
||||
count = tty->receive_room;
|
||||
char_buf = head->char_buf_ptr + head->read;
|
||||
flag_buf = head->flag_buf_ptr + head->read;
|
||||
head->read += count;
|
||||
spin_unlock_irqrestore(&tty->buf.lock, flags);
|
||||
disc->ops->receive_buf(tty, char_buf,
|
||||
flag_buf, count);
|
||||
spin_lock_irqsave(&tty->buf.lock, flags);
|
||||
}
|
||||
clear_bit(TTY_FLUSHING, &tty->flags);
|
||||
}
|
||||
|
||||
/* We may have a deferred request to flush the input buffer,
|
||||
if so pull the chain under the lock and empty the queue */
|
||||
if (test_bit(TTY_FLUSHPENDING, &tty->flags)) {
|
||||
__tty_buffer_flush(tty);
|
||||
clear_bit(TTY_FLUSHPENDING, &tty->flags);
|
||||
wake_up(&tty->read_wait);
|
||||
}
|
||||
spin_unlock_irqrestore(&tty->buf.lock, flags);
|
||||
|
||||
tty_ldisc_deref(disc);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_flush_to_ldisc
|
||||
* @tty: tty to push
|
||||
*
|
||||
* Push the terminal flip buffers to the line discipline.
|
||||
*
|
||||
* Must not be called from IRQ context.
|
||||
*/
|
||||
void tty_flush_to_ldisc(struct tty_struct *tty)
|
||||
{
|
||||
flush_delayed_work(&tty->buf.work);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_flip_buffer_push - terminal
|
||||
* @tty: tty to push
|
||||
*
|
||||
* Queue a push of the terminal flip buffers to the line discipline. This
|
||||
* function must not be called from IRQ context if tty->low_latency is set.
|
||||
*
|
||||
* In the event of the queue being busy for flipping the work will be
|
||||
* held off and retried later.
|
||||
*
|
||||
* Locking: tty buffer lock. Driver locks in low latency mode.
|
||||
*/
|
||||
|
||||
void tty_flip_buffer_push(struct tty_struct *tty)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tty->buf.lock, flags);
|
||||
if (tty->buf.tail != NULL)
|
||||
tty->buf.tail->commit = tty->buf.tail->used;
|
||||
spin_unlock_irqrestore(&tty->buf.lock, flags);
|
||||
|
||||
if (tty->low_latency)
|
||||
flush_to_ldisc(&tty->buf.work.work);
|
||||
else
|
||||
schedule_delayed_work(&tty->buf.work, 1);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_flip_buffer_push);
|
||||
|
||||
/**
|
||||
* tty_buffer_init - prepare a tty buffer structure
|
||||
* @tty: tty to initialise
|
||||
*
|
||||
* Set up the initial state of the buffer management for a tty device.
|
||||
* Must be called before the other tty buffer functions are used.
|
||||
*
|
||||
* Locking: none
|
||||
*/
|
||||
|
||||
void tty_buffer_init(struct tty_struct *tty)
|
||||
{
|
||||
spin_lock_init(&tty->buf.lock);
|
||||
tty->buf.head = NULL;
|
||||
tty->buf.tail = NULL;
|
||||
tty->buf.free = NULL;
|
||||
tty->buf.memory_used = 0;
|
||||
INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc);
|
||||
}
|
||||
|
3263
drivers/tty/tty_io.c
Normal file
3263
drivers/tty/tty_io.c
Normal file
File diff suppressed because it is too large
Load Diff
1179
drivers/tty/tty_ioctl.c
Normal file
1179
drivers/tty/tty_ioctl.c
Normal file
File diff suppressed because it is too large
Load Diff
915
drivers/tty/tty_ldisc.c
Normal file
915
drivers/tty/tty_ldisc.c
Normal file
@@ -0,0 +1,915 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/major.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/signal.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_driver.h>
|
||||
#include <linux/tty_flip.h>
|
||||
#include <linux/devpts_fs.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/kd.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/system.h>
|
||||
|
||||
#include <linux/kbd_kern.h>
|
||||
#include <linux/vt_kern.h>
|
||||
#include <linux/selection.h>
|
||||
|
||||
#include <linux/smp_lock.h> /* For the moment */
|
||||
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/nsproxy.h>
|
||||
|
||||
/*
|
||||
* This guards the refcounted line discipline lists. The lock
|
||||
* must be taken with irqs off because there are hangup path
|
||||
* callers who will do ldisc lookups and cannot sleep.
|
||||
*/
|
||||
|
||||
static DEFINE_SPINLOCK(tty_ldisc_lock);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait);
|
||||
/* Line disc dispatch table */
|
||||
static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
|
||||
|
||||
static inline struct tty_ldisc *get_ldisc(struct tty_ldisc *ld)
|
||||
{
|
||||
if (ld)
|
||||
atomic_inc(&ld->users);
|
||||
return ld;
|
||||
}
|
||||
|
||||
static void put_ldisc(struct tty_ldisc *ld)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (WARN_ON_ONCE(!ld))
|
||||
return;
|
||||
|
||||
/*
|
||||
* If this is the last user, free the ldisc, and
|
||||
* release the ldisc ops.
|
||||
*
|
||||
* We really want an "atomic_dec_and_lock_irqsave()",
|
||||
* but we don't have it, so this does it by hand.
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
if (atomic_dec_and_lock(&ld->users, &tty_ldisc_lock)) {
|
||||
struct tty_ldisc_ops *ldo = ld->ops;
|
||||
|
||||
ldo->refcount--;
|
||||
module_put(ldo->owner);
|
||||
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
|
||||
|
||||
kfree(ld);
|
||||
return;
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_register_ldisc - install a line discipline
|
||||
* @disc: ldisc number
|
||||
* @new_ldisc: pointer to the ldisc object
|
||||
*
|
||||
* Installs a new line discipline into the kernel. The discipline
|
||||
* is set up as unreferenced and then made available to the kernel
|
||||
* from this point onwards.
|
||||
*
|
||||
* Locking:
|
||||
* takes tty_ldisc_lock to guard against ldisc races
|
||||
*/
|
||||
|
||||
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
if (disc < N_TTY || disc >= NR_LDISCS)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&tty_ldisc_lock, flags);
|
||||
tty_ldiscs[disc] = new_ldisc;
|
||||
new_ldisc->num = disc;
|
||||
new_ldisc->refcount = 0;
|
||||
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(tty_register_ldisc);
|
||||
|
||||
/**
|
||||
* tty_unregister_ldisc - unload a line discipline
|
||||
* @disc: ldisc number
|
||||
* @new_ldisc: pointer to the ldisc object
|
||||
*
|
||||
* Remove a line discipline from the kernel providing it is not
|
||||
* currently in use.
|
||||
*
|
||||
* Locking:
|
||||
* takes tty_ldisc_lock to guard against ldisc races
|
||||
*/
|
||||
|
||||
int tty_unregister_ldisc(int disc)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
if (disc < N_TTY || disc >= NR_LDISCS)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&tty_ldisc_lock, flags);
|
||||
if (tty_ldiscs[disc]->refcount)
|
||||
ret = -EBUSY;
|
||||
else
|
||||
tty_ldiscs[disc] = NULL;
|
||||
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(tty_unregister_ldisc);
|
||||
|
||||
static struct tty_ldisc_ops *get_ldops(int disc)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct tty_ldisc_ops *ldops, *ret;
|
||||
|
||||
spin_lock_irqsave(&tty_ldisc_lock, flags);
|
||||
ret = ERR_PTR(-EINVAL);
|
||||
ldops = tty_ldiscs[disc];
|
||||
if (ldops) {
|
||||
ret = ERR_PTR(-EAGAIN);
|
||||
if (try_module_get(ldops->owner)) {
|
||||
ldops->refcount++;
|
||||
ret = ldops;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void put_ldops(struct tty_ldisc_ops *ldops)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&tty_ldisc_lock, flags);
|
||||
ldops->refcount--;
|
||||
module_put(ldops->owner);
|
||||
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_get - take a reference to an ldisc
|
||||
* @disc: ldisc number
|
||||
*
|
||||
* Takes a reference to a line discipline. Deals with refcounts and
|
||||
* module locking counts. Returns NULL if the discipline is not available.
|
||||
* Returns a pointer to the discipline and bumps the ref count if it is
|
||||
* available
|
||||
*
|
||||
* Locking:
|
||||
* takes tty_ldisc_lock to guard against ldisc races
|
||||
*/
|
||||
|
||||
static struct tty_ldisc *tty_ldisc_get(int disc)
|
||||
{
|
||||
struct tty_ldisc *ld;
|
||||
struct tty_ldisc_ops *ldops;
|
||||
|
||||
if (disc < N_TTY || disc >= NR_LDISCS)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
/*
|
||||
* Get the ldisc ops - we may need to request them to be loaded
|
||||
* dynamically and try again.
|
||||
*/
|
||||
ldops = get_ldops(disc);
|
||||
if (IS_ERR(ldops)) {
|
||||
request_module("tty-ldisc-%d", disc);
|
||||
ldops = get_ldops(disc);
|
||||
if (IS_ERR(ldops))
|
||||
return ERR_CAST(ldops);
|
||||
}
|
||||
|
||||
ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL);
|
||||
if (ld == NULL) {
|
||||
put_ldops(ldops);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
ld->ops = ldops;
|
||||
atomic_set(&ld->users, 1);
|
||||
return ld;
|
||||
}
|
||||
|
||||
static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos)
|
||||
{
|
||||
return (*pos < NR_LDISCS) ? pos : NULL;
|
||||
}
|
||||
|
||||
static void *tty_ldiscs_seq_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
{
|
||||
(*pos)++;
|
||||
return (*pos < NR_LDISCS) ? pos : NULL;
|
||||
}
|
||||
|
||||
static void tty_ldiscs_seq_stop(struct seq_file *m, void *v)
|
||||
{
|
||||
}
|
||||
|
||||
static int tty_ldiscs_seq_show(struct seq_file *m, void *v)
|
||||
{
|
||||
int i = *(loff_t *)v;
|
||||
struct tty_ldisc_ops *ldops;
|
||||
|
||||
ldops = get_ldops(i);
|
||||
if (IS_ERR(ldops))
|
||||
return 0;
|
||||
seq_printf(m, "%-10s %2d\n", ldops->name ? ldops->name : "???", i);
|
||||
put_ldops(ldops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations tty_ldiscs_seq_ops = {
|
||||
.start = tty_ldiscs_seq_start,
|
||||
.next = tty_ldiscs_seq_next,
|
||||
.stop = tty_ldiscs_seq_stop,
|
||||
.show = tty_ldiscs_seq_show,
|
||||
};
|
||||
|
||||
static int proc_tty_ldiscs_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return seq_open(file, &tty_ldiscs_seq_ops);
|
||||
}
|
||||
|
||||
const struct file_operations tty_ldiscs_proc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = proc_tty_ldiscs_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
/**
|
||||
* tty_ldisc_assign - set ldisc on a tty
|
||||
* @tty: tty to assign
|
||||
* @ld: line discipline
|
||||
*
|
||||
* Install an instance of a line discipline into a tty structure. The
|
||||
* ldisc must have a reference count above zero to ensure it remains.
|
||||
* The tty instance refcount starts at zero.
|
||||
*
|
||||
* Locking:
|
||||
* Caller must hold references
|
||||
*/
|
||||
|
||||
static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
|
||||
{
|
||||
tty->ldisc = ld;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_try - internal helper
|
||||
* @tty: the tty
|
||||
*
|
||||
* Make a single attempt to grab and bump the refcount on
|
||||
* the tty ldisc. Return 0 on failure or 1 on success. This is
|
||||
* used to implement both the waiting and non waiting versions
|
||||
* of tty_ldisc_ref
|
||||
*
|
||||
* Locking: takes tty_ldisc_lock
|
||||
*/
|
||||
|
||||
static struct tty_ldisc *tty_ldisc_try(struct tty_struct *tty)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct tty_ldisc *ld;
|
||||
|
||||
spin_lock_irqsave(&tty_ldisc_lock, flags);
|
||||
ld = NULL;
|
||||
if (test_bit(TTY_LDISC, &tty->flags))
|
||||
ld = get_ldisc(tty->ldisc);
|
||||
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
|
||||
return ld;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_ref_wait - wait for the tty ldisc
|
||||
* @tty: tty device
|
||||
*
|
||||
* Dereference the line discipline for the terminal and take a
|
||||
* reference to it. If the line discipline is in flux then
|
||||
* wait patiently until it changes.
|
||||
*
|
||||
* Note: Must not be called from an IRQ/timer context. The caller
|
||||
* must also be careful not to hold other locks that will deadlock
|
||||
* against a discipline change, such as an existing ldisc reference
|
||||
* (which we check for)
|
||||
*
|
||||
* Locking: call functions take tty_ldisc_lock
|
||||
*/
|
||||
|
||||
struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
|
||||
{
|
||||
struct tty_ldisc *ld;
|
||||
|
||||
/* wait_event is a macro */
|
||||
wait_event(tty_ldisc_wait, (ld = tty_ldisc_try(tty)) != NULL);
|
||||
return ld;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
|
||||
|
||||
/**
|
||||
* tty_ldisc_ref - get the tty ldisc
|
||||
* @tty: tty device
|
||||
*
|
||||
* Dereference the line discipline for the terminal and take a
|
||||
* reference to it. If the line discipline is in flux then
|
||||
* return NULL. Can be called from IRQ and timer functions.
|
||||
*
|
||||
* Locking: called functions take tty_ldisc_lock
|
||||
*/
|
||||
|
||||
struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
|
||||
{
|
||||
return tty_ldisc_try(tty);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tty_ldisc_ref);
|
||||
|
||||
/**
|
||||
* tty_ldisc_deref - free a tty ldisc reference
|
||||
* @ld: reference to free up
|
||||
*
|
||||
* Undoes the effect of tty_ldisc_ref or tty_ldisc_ref_wait. May
|
||||
* be called in IRQ context.
|
||||
*
|
||||
* Locking: takes tty_ldisc_lock
|
||||
*/
|
||||
|
||||
void tty_ldisc_deref(struct tty_ldisc *ld)
|
||||
{
|
||||
put_ldisc(ld);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tty_ldisc_deref);
|
||||
|
||||
static inline void tty_ldisc_put(struct tty_ldisc *ld)
|
||||
{
|
||||
put_ldisc(ld);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_enable - allow ldisc use
|
||||
* @tty: terminal to activate ldisc on
|
||||
*
|
||||
* Set the TTY_LDISC flag when the line discipline can be called
|
||||
* again. Do necessary wakeups for existing sleepers. Clear the LDISC
|
||||
* changing flag to indicate any ldisc change is now over.
|
||||
*
|
||||
* Note: nobody should set the TTY_LDISC bit except via this function.
|
||||
* Clearing directly is allowed.
|
||||
*/
|
||||
|
||||
void tty_ldisc_enable(struct tty_struct *tty)
|
||||
{
|
||||
set_bit(TTY_LDISC, &tty->flags);
|
||||
clear_bit(TTY_LDISC_CHANGING, &tty->flags);
|
||||
wake_up(&tty_ldisc_wait);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_flush - flush line discipline queue
|
||||
* @tty: tty
|
||||
*
|
||||
* Flush the line discipline queue (if any) for this tty. If there
|
||||
* is no line discipline active this is a no-op.
|
||||
*/
|
||||
|
||||
void tty_ldisc_flush(struct tty_struct *tty)
|
||||
{
|
||||
struct tty_ldisc *ld = tty_ldisc_ref(tty);
|
||||
if (ld) {
|
||||
if (ld->ops->flush_buffer)
|
||||
ld->ops->flush_buffer(tty);
|
||||
tty_ldisc_deref(ld);
|
||||
}
|
||||
tty_buffer_flush(tty);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tty_ldisc_flush);
|
||||
|
||||
/**
|
||||
* tty_set_termios_ldisc - set ldisc field
|
||||
* @tty: tty structure
|
||||
* @num: line discipline number
|
||||
*
|
||||
* This is probably overkill for real world processors but
|
||||
* they are not on hot paths so a little discipline won't do
|
||||
* any harm.
|
||||
*
|
||||
* Locking: takes termios_mutex
|
||||
*/
|
||||
|
||||
static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
|
||||
{
|
||||
mutex_lock(&tty->termios_mutex);
|
||||
tty->termios->c_line = num;
|
||||
mutex_unlock(&tty->termios_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_open - open a line discipline
|
||||
* @tty: tty we are opening the ldisc on
|
||||
* @ld: discipline to open
|
||||
*
|
||||
* A helper opening method. Also a convenient debugging and check
|
||||
* point.
|
||||
*
|
||||
* Locking: always called with BTM already held.
|
||||
*/
|
||||
|
||||
static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
|
||||
{
|
||||
WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));
|
||||
if (ld->ops->open) {
|
||||
int ret;
|
||||
/* BTM here locks versus a hangup event */
|
||||
WARN_ON(!tty_locked());
|
||||
ret = ld->ops->open(tty);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_close - close a line discipline
|
||||
* @tty: tty we are opening the ldisc on
|
||||
* @ld: discipline to close
|
||||
*
|
||||
* A helper close method. Also a convenient debugging and check
|
||||
* point.
|
||||
*/
|
||||
|
||||
static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
|
||||
{
|
||||
WARN_ON(!test_bit(TTY_LDISC_OPEN, &tty->flags));
|
||||
clear_bit(TTY_LDISC_OPEN, &tty->flags);
|
||||
if (ld->ops->close)
|
||||
ld->ops->close(tty);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_restore - helper for tty ldisc change
|
||||
* @tty: tty to recover
|
||||
* @old: previous ldisc
|
||||
*
|
||||
* Restore the previous line discipline or N_TTY when a line discipline
|
||||
* change fails due to an open error
|
||||
*/
|
||||
|
||||
static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
|
||||
{
|
||||
char buf[64];
|
||||
struct tty_ldisc *new_ldisc;
|
||||
int r;
|
||||
|
||||
/* There is an outstanding reference here so this is safe */
|
||||
old = tty_ldisc_get(old->ops->num);
|
||||
WARN_ON(IS_ERR(old));
|
||||
tty_ldisc_assign(tty, old);
|
||||
tty_set_termios_ldisc(tty, old->ops->num);
|
||||
if (tty_ldisc_open(tty, old) < 0) {
|
||||
tty_ldisc_put(old);
|
||||
/* This driver is always present */
|
||||
new_ldisc = tty_ldisc_get(N_TTY);
|
||||
if (IS_ERR(new_ldisc))
|
||||
panic("n_tty: get");
|
||||
tty_ldisc_assign(tty, new_ldisc);
|
||||
tty_set_termios_ldisc(tty, N_TTY);
|
||||
r = tty_ldisc_open(tty, new_ldisc);
|
||||
if (r < 0)
|
||||
panic("Couldn't open N_TTY ldisc for "
|
||||
"%s --- error %d.",
|
||||
tty_name(tty, buf), r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_halt - shut down the line discipline
|
||||
* @tty: tty device
|
||||
*
|
||||
* Shut down the line discipline and work queue for this tty device.
|
||||
* The TTY_LDISC flag being cleared ensures no further references can
|
||||
* be obtained while the delayed work queue halt ensures that no more
|
||||
* data is fed to the ldisc.
|
||||
*
|
||||
* You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex)
|
||||
* in order to make sure any currently executing ldisc work is also
|
||||
* flushed.
|
||||
*/
|
||||
|
||||
static int tty_ldisc_halt(struct tty_struct *tty)
|
||||
{
|
||||
clear_bit(TTY_LDISC, &tty->flags);
|
||||
return cancel_delayed_work_sync(&tty->buf.work);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_set_ldisc - set line discipline
|
||||
* @tty: the terminal to set
|
||||
* @ldisc: the line discipline
|
||||
*
|
||||
* Set the discipline of a tty line. Must be called from a process
|
||||
* context. The ldisc change logic has to protect itself against any
|
||||
* overlapping ldisc change (including on the other end of pty pairs),
|
||||
* the close of one side of a tty/pty pair, and eventually hangup.
|
||||
*
|
||||
* Locking: takes tty_ldisc_lock, termios_mutex
|
||||
*/
|
||||
|
||||
int tty_set_ldisc(struct tty_struct *tty, int ldisc)
|
||||
{
|
||||
int retval;
|
||||
struct tty_ldisc *o_ldisc, *new_ldisc;
|
||||
int work, o_work = 0;
|
||||
struct tty_struct *o_tty;
|
||||
|
||||
new_ldisc = tty_ldisc_get(ldisc);
|
||||
if (IS_ERR(new_ldisc))
|
||||
return PTR_ERR(new_ldisc);
|
||||
|
||||
tty_lock();
|
||||
/*
|
||||
* We need to look at the tty locking here for pty/tty pairs
|
||||
* when both sides try to change in parallel.
|
||||
*/
|
||||
|
||||
o_tty = tty->link; /* o_tty is the pty side or NULL */
|
||||
|
||||
|
||||
/*
|
||||
* Check the no-op case
|
||||
*/
|
||||
|
||||
if (tty->ldisc->ops->num == ldisc) {
|
||||
tty_unlock();
|
||||
tty_ldisc_put(new_ldisc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tty_unlock();
|
||||
/*
|
||||
* Problem: What do we do if this blocks ?
|
||||
* We could deadlock here
|
||||
*/
|
||||
|
||||
tty_wait_until_sent(tty, 0);
|
||||
|
||||
tty_lock();
|
||||
mutex_lock(&tty->ldisc_mutex);
|
||||
|
||||
/*
|
||||
* We could be midstream of another ldisc change which has
|
||||
* dropped the lock during processing. If so we need to wait.
|
||||
*/
|
||||
|
||||
while (test_bit(TTY_LDISC_CHANGING, &tty->flags)) {
|
||||
mutex_unlock(&tty->ldisc_mutex);
|
||||
tty_unlock();
|
||||
wait_event(tty_ldisc_wait,
|
||||
test_bit(TTY_LDISC_CHANGING, &tty->flags) == 0);
|
||||
tty_lock();
|
||||
mutex_lock(&tty->ldisc_mutex);
|
||||
}
|
||||
|
||||
set_bit(TTY_LDISC_CHANGING, &tty->flags);
|
||||
|
||||
/*
|
||||
* No more input please, we are switching. The new ldisc
|
||||
* will update this value in the ldisc open function
|
||||
*/
|
||||
|
||||
tty->receive_room = 0;
|
||||
|
||||
o_ldisc = tty->ldisc;
|
||||
|
||||
tty_unlock();
|
||||
/*
|
||||
* Make sure we don't change while someone holds a
|
||||
* reference to the line discipline. The TTY_LDISC bit
|
||||
* prevents anyone taking a reference once it is clear.
|
||||
* We need the lock to avoid racing reference takers.
|
||||
*
|
||||
* We must clear the TTY_LDISC bit here to avoid a livelock
|
||||
* with a userspace app continually trying to use the tty in
|
||||
* parallel to the change and re-referencing the tty.
|
||||
*/
|
||||
|
||||
work = tty_ldisc_halt(tty);
|
||||
if (o_tty)
|
||||
o_work = tty_ldisc_halt(o_tty);
|
||||
|
||||
/*
|
||||
* Wait for ->hangup_work and ->buf.work handlers to terminate.
|
||||
* We must drop the mutex here in case a hangup is also in process.
|
||||
*/
|
||||
|
||||
mutex_unlock(&tty->ldisc_mutex);
|
||||
|
||||
flush_scheduled_work();
|
||||
|
||||
tty_lock();
|
||||
mutex_lock(&tty->ldisc_mutex);
|
||||
if (test_bit(TTY_HUPPED, &tty->flags)) {
|
||||
/* We were raced by the hangup method. It will have stomped
|
||||
the ldisc data and closed the ldisc down */
|
||||
clear_bit(TTY_LDISC_CHANGING, &tty->flags);
|
||||
mutex_unlock(&tty->ldisc_mutex);
|
||||
tty_ldisc_put(new_ldisc);
|
||||
tty_unlock();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Shutdown the current discipline. */
|
||||
tty_ldisc_close(tty, o_ldisc);
|
||||
|
||||
/* Now set up the new line discipline. */
|
||||
tty_ldisc_assign(tty, new_ldisc);
|
||||
tty_set_termios_ldisc(tty, ldisc);
|
||||
|
||||
retval = tty_ldisc_open(tty, new_ldisc);
|
||||
if (retval < 0) {
|
||||
/* Back to the old one or N_TTY if we can't */
|
||||
tty_ldisc_put(new_ldisc);
|
||||
tty_ldisc_restore(tty, o_ldisc);
|
||||
}
|
||||
|
||||
/* At this point we hold a reference to the new ldisc and a
|
||||
a reference to the old ldisc. If we ended up flipping back
|
||||
to the existing ldisc we have two references to it */
|
||||
|
||||
if (tty->ldisc->ops->num != o_ldisc->ops->num && tty->ops->set_ldisc)
|
||||
tty->ops->set_ldisc(tty);
|
||||
|
||||
tty_ldisc_put(o_ldisc);
|
||||
|
||||
/*
|
||||
* Allow ldisc referencing to occur again
|
||||
*/
|
||||
|
||||
tty_ldisc_enable(tty);
|
||||
if (o_tty)
|
||||
tty_ldisc_enable(o_tty);
|
||||
|
||||
/* Restart the work queue in case no characters kick it off. Safe if
|
||||
already running */
|
||||
if (work)
|
||||
schedule_delayed_work(&tty->buf.work, 1);
|
||||
if (o_work)
|
||||
schedule_delayed_work(&o_tty->buf.work, 1);
|
||||
mutex_unlock(&tty->ldisc_mutex);
|
||||
tty_unlock();
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_reset_termios - reset terminal state
|
||||
* @tty: tty to reset
|
||||
*
|
||||
* Restore a terminal to the driver default state.
|
||||
*/
|
||||
|
||||
static void tty_reset_termios(struct tty_struct *tty)
|
||||
{
|
||||
mutex_lock(&tty->termios_mutex);
|
||||
*tty->termios = tty->driver->init_termios;
|
||||
tty->termios->c_ispeed = tty_termios_input_baud_rate(tty->termios);
|
||||
tty->termios->c_ospeed = tty_termios_baud_rate(tty->termios);
|
||||
mutex_unlock(&tty->termios_mutex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* tty_ldisc_reinit - reinitialise the tty ldisc
|
||||
* @tty: tty to reinit
|
||||
* @ldisc: line discipline to reinitialize
|
||||
*
|
||||
* Switch the tty to a line discipline and leave the ldisc
|
||||
* state closed
|
||||
*/
|
||||
|
||||
static void tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
|
||||
{
|
||||
struct tty_ldisc *ld;
|
||||
|
||||
tty_ldisc_close(tty, tty->ldisc);
|
||||
tty_ldisc_put(tty->ldisc);
|
||||
tty->ldisc = NULL;
|
||||
/*
|
||||
* Switch the line discipline back
|
||||
*/
|
||||
ld = tty_ldisc_get(ldisc);
|
||||
BUG_ON(IS_ERR(ld));
|
||||
tty_ldisc_assign(tty, ld);
|
||||
tty_set_termios_ldisc(tty, ldisc);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_hangup - hangup ldisc reset
|
||||
* @tty: tty being hung up
|
||||
*
|
||||
* Some tty devices reset their termios when they receive a hangup
|
||||
* event. In that situation we must also switch back to N_TTY properly
|
||||
* before we reset the termios data.
|
||||
*
|
||||
* Locking: We can take the ldisc mutex as the rest of the code is
|
||||
* careful to allow for this.
|
||||
*
|
||||
* In the pty pair case this occurs in the close() path of the
|
||||
* tty itself so we must be careful about locking rules.
|
||||
*/
|
||||
|
||||
void tty_ldisc_hangup(struct tty_struct *tty)
|
||||
{
|
||||
struct tty_ldisc *ld;
|
||||
int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS;
|
||||
int err = 0;
|
||||
|
||||
/*
|
||||
* FIXME! What are the locking issues here? This may me overdoing
|
||||
* things... This question is especially important now that we've
|
||||
* removed the irqlock.
|
||||
*/
|
||||
ld = tty_ldisc_ref(tty);
|
||||
if (ld != NULL) {
|
||||
/* We may have no line discipline at this point */
|
||||
if (ld->ops->flush_buffer)
|
||||
ld->ops->flush_buffer(tty);
|
||||
tty_driver_flush_buffer(tty);
|
||||
if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
|
||||
ld->ops->write_wakeup)
|
||||
ld->ops->write_wakeup(tty);
|
||||
if (ld->ops->hangup)
|
||||
ld->ops->hangup(tty);
|
||||
tty_ldisc_deref(ld);
|
||||
}
|
||||
/*
|
||||
* FIXME: Once we trust the LDISC code better we can wait here for
|
||||
* ldisc completion and fix the driver call race
|
||||
*/
|
||||
wake_up_interruptible_poll(&tty->write_wait, POLLOUT);
|
||||
wake_up_interruptible_poll(&tty->read_wait, POLLIN);
|
||||
/*
|
||||
* Shutdown the current line discipline, and reset it to
|
||||
* N_TTY if need be.
|
||||
*
|
||||
* Avoid racing set_ldisc or tty_ldisc_release
|
||||
*/
|
||||
mutex_lock(&tty->ldisc_mutex);
|
||||
|
||||
/*
|
||||
* this is like tty_ldisc_halt, but we need to give up
|
||||
* the BTM before calling cancel_delayed_work_sync,
|
||||
* which may need to wait for another function taking the BTM
|
||||
*/
|
||||
clear_bit(TTY_LDISC, &tty->flags);
|
||||
tty_unlock();
|
||||
cancel_delayed_work_sync(&tty->buf.work);
|
||||
mutex_unlock(&tty->ldisc_mutex);
|
||||
|
||||
tty_lock();
|
||||
mutex_lock(&tty->ldisc_mutex);
|
||||
|
||||
/* At this point we have a closed ldisc and we want to
|
||||
reopen it. We could defer this to the next open but
|
||||
it means auditing a lot of other paths so this is
|
||||
a FIXME */
|
||||
if (tty->ldisc) { /* Not yet closed */
|
||||
if (reset == 0) {
|
||||
tty_ldisc_reinit(tty, tty->termios->c_line);
|
||||
err = tty_ldisc_open(tty, tty->ldisc);
|
||||
}
|
||||
/* If the re-open fails or we reset then go to N_TTY. The
|
||||
N_TTY open cannot fail */
|
||||
if (reset || err) {
|
||||
tty_ldisc_reinit(tty, N_TTY);
|
||||
WARN_ON(tty_ldisc_open(tty, tty->ldisc));
|
||||
}
|
||||
tty_ldisc_enable(tty);
|
||||
}
|
||||
mutex_unlock(&tty->ldisc_mutex);
|
||||
if (reset)
|
||||
tty_reset_termios(tty);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_setup - open line discipline
|
||||
* @tty: tty being shut down
|
||||
* @o_tty: pair tty for pty/tty pairs
|
||||
*
|
||||
* Called during the initial open of a tty/pty pair in order to set up the
|
||||
* line disciplines and bind them to the tty. This has no locking issues
|
||||
* as the device isn't yet active.
|
||||
*/
|
||||
|
||||
int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
|
||||
{
|
||||
struct tty_ldisc *ld = tty->ldisc;
|
||||
int retval;
|
||||
|
||||
retval = tty_ldisc_open(tty, ld);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (o_tty) {
|
||||
retval = tty_ldisc_open(o_tty, o_tty->ldisc);
|
||||
if (retval) {
|
||||
tty_ldisc_close(tty, ld);
|
||||
return retval;
|
||||
}
|
||||
tty_ldisc_enable(o_tty);
|
||||
}
|
||||
tty_ldisc_enable(tty);
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* tty_ldisc_release - release line discipline
|
||||
* @tty: tty being shut down
|
||||
* @o_tty: pair tty for pty/tty pairs
|
||||
*
|
||||
* Called during the final close of a tty/pty pair in order to shut down
|
||||
* the line discpline layer. On exit the ldisc assigned is N_TTY and the
|
||||
* ldisc has not been opened.
|
||||
*/
|
||||
|
||||
void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty)
|
||||
{
|
||||
/*
|
||||
* Prevent flush_to_ldisc() from rescheduling the work for later. Then
|
||||
* kill any delayed work. As this is the final close it does not
|
||||
* race with the set_ldisc code path.
|
||||
*/
|
||||
|
||||
tty_unlock();
|
||||
tty_ldisc_halt(tty);
|
||||
flush_scheduled_work();
|
||||
tty_lock();
|
||||
|
||||
mutex_lock(&tty->ldisc_mutex);
|
||||
/*
|
||||
* Now kill off the ldisc
|
||||
*/
|
||||
tty_ldisc_close(tty, tty->ldisc);
|
||||
tty_ldisc_put(tty->ldisc);
|
||||
/* Force an oops if we mess this up */
|
||||
tty->ldisc = NULL;
|
||||
|
||||
/* Ensure the next open requests the N_TTY ldisc */
|
||||
tty_set_termios_ldisc(tty, N_TTY);
|
||||
mutex_unlock(&tty->ldisc_mutex);
|
||||
|
||||
/* This will need doing differently if we need to lock */
|
||||
if (o_tty)
|
||||
tty_ldisc_release(o_tty, NULL);
|
||||
|
||||
/* And the memory resources remaining (buffers, termios) will be
|
||||
disposed of when the kref hits zero */
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_ldisc_init - ldisc setup for new tty
|
||||
* @tty: tty being allocated
|
||||
*
|
||||
* Set up the line discipline objects for a newly allocated tty. Note that
|
||||
* the tty structure is not completely set up when this call is made.
|
||||
*/
|
||||
|
||||
void tty_ldisc_init(struct tty_struct *tty)
|
||||
{
|
||||
struct tty_ldisc *ld = tty_ldisc_get(N_TTY);
|
||||
if (IS_ERR(ld))
|
||||
panic("n_tty: init_tty");
|
||||
tty_ldisc_assign(tty, ld);
|
||||
}
|
||||
|
||||
void tty_ldisc_begin(void)
|
||||
{
|
||||
/* Setup the default TTY line discipline. */
|
||||
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
|
||||
}
|
47
drivers/tty/tty_mutex.c
Normal file
47
drivers/tty/tty_mutex.c
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* drivers/char/tty_lock.c
|
||||
*/
|
||||
#include <linux/tty.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
/*
|
||||
* The 'big tty mutex'
|
||||
*
|
||||
* This mutex is taken and released by tty_lock() and tty_unlock(),
|
||||
* replacing the older big kernel lock.
|
||||
* It can no longer be taken recursively, and does not get
|
||||
* released implicitly while sleeping.
|
||||
*
|
||||
* Don't use in new code.
|
||||
*/
|
||||
static DEFINE_MUTEX(big_tty_mutex);
|
||||
struct task_struct *__big_tty_mutex_owner;
|
||||
EXPORT_SYMBOL_GPL(__big_tty_mutex_owner);
|
||||
|
||||
/*
|
||||
* Getting the big tty mutex.
|
||||
*/
|
||||
void __lockfunc tty_lock(void)
|
||||
{
|
||||
struct task_struct *task = current;
|
||||
|
||||
WARN_ON(__big_tty_mutex_owner == task);
|
||||
|
||||
mutex_lock(&big_tty_mutex);
|
||||
__big_tty_mutex_owner = task;
|
||||
}
|
||||
EXPORT_SYMBOL(tty_lock);
|
||||
|
||||
void __lockfunc tty_unlock(void)
|
||||
{
|
||||
struct task_struct *task = current;
|
||||
|
||||
WARN_ON(__big_tty_mutex_owner != task);
|
||||
__big_tty_mutex_owner = NULL;
|
||||
|
||||
mutex_unlock(&big_tty_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_unlock);
|
446
drivers/tty/tty_port.c
Normal file
446
drivers/tty/tty_port.c
Normal file
@@ -0,0 +1,446 @@
|
||||
/*
|
||||
* Tty port functions
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_driver.h>
|
||||
#include <linux/tty_flip.h>
|
||||
#include <linux/serial.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
void tty_port_init(struct tty_port *port)
|
||||
{
|
||||
memset(port, 0, sizeof(*port));
|
||||
init_waitqueue_head(&port->open_wait);
|
||||
init_waitqueue_head(&port->close_wait);
|
||||
init_waitqueue_head(&port->delta_msr_wait);
|
||||
mutex_init(&port->mutex);
|
||||
mutex_init(&port->buf_mutex);
|
||||
spin_lock_init(&port->lock);
|
||||
port->close_delay = (50 * HZ) / 100;
|
||||
port->closing_wait = (3000 * HZ) / 100;
|
||||
kref_init(&port->kref);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_init);
|
||||
|
||||
int tty_port_alloc_xmit_buf(struct tty_port *port)
|
||||
{
|
||||
/* We may sleep in get_zeroed_page() */
|
||||
mutex_lock(&port->buf_mutex);
|
||||
if (port->xmit_buf == NULL)
|
||||
port->xmit_buf = (unsigned char *)get_zeroed_page(GFP_KERNEL);
|
||||
mutex_unlock(&port->buf_mutex);
|
||||
if (port->xmit_buf == NULL)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_alloc_xmit_buf);
|
||||
|
||||
void tty_port_free_xmit_buf(struct tty_port *port)
|
||||
{
|
||||
mutex_lock(&port->buf_mutex);
|
||||
if (port->xmit_buf != NULL) {
|
||||
free_page((unsigned long)port->xmit_buf);
|
||||
port->xmit_buf = NULL;
|
||||
}
|
||||
mutex_unlock(&port->buf_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_free_xmit_buf);
|
||||
|
||||
static void tty_port_destructor(struct kref *kref)
|
||||
{
|
||||
struct tty_port *port = container_of(kref, struct tty_port, kref);
|
||||
if (port->xmit_buf)
|
||||
free_page((unsigned long)port->xmit_buf);
|
||||
if (port->ops->destruct)
|
||||
port->ops->destruct(port);
|
||||
else
|
||||
kfree(port);
|
||||
}
|
||||
|
||||
void tty_port_put(struct tty_port *port)
|
||||
{
|
||||
if (port)
|
||||
kref_put(&port->kref, tty_port_destructor);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_put);
|
||||
|
||||
/**
|
||||
* tty_port_tty_get - get a tty reference
|
||||
* @port: tty port
|
||||
*
|
||||
* Return a refcount protected tty instance or NULL if the port is not
|
||||
* associated with a tty (eg due to close or hangup)
|
||||
*/
|
||||
|
||||
struct tty_struct *tty_port_tty_get(struct tty_port *port)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct tty_struct *tty;
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
tty = tty_kref_get(port->tty);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
return tty;
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_tty_get);
|
||||
|
||||
/**
|
||||
* tty_port_tty_set - set the tty of a port
|
||||
* @port: tty port
|
||||
* @tty: the tty
|
||||
*
|
||||
* Associate the port and tty pair. Manages any internal refcounts.
|
||||
* Pass NULL to deassociate a port
|
||||
*/
|
||||
|
||||
void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
if (port->tty)
|
||||
tty_kref_put(port->tty);
|
||||
port->tty = tty_kref_get(tty);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_tty_set);
|
||||
|
||||
static void tty_port_shutdown(struct tty_port *port)
|
||||
{
|
||||
mutex_lock(&port->mutex);
|
||||
if (port->ops->shutdown && !port->console &&
|
||||
test_and_clear_bit(ASYNCB_INITIALIZED, &port->flags))
|
||||
port->ops->shutdown(port);
|
||||
mutex_unlock(&port->mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* tty_port_hangup - hangup helper
|
||||
* @port: tty port
|
||||
*
|
||||
* Perform port level tty hangup flag and count changes. Drop the tty
|
||||
* reference.
|
||||
*/
|
||||
|
||||
void tty_port_hangup(struct tty_port *port)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
port->count = 0;
|
||||
port->flags &= ~ASYNC_NORMAL_ACTIVE;
|
||||
if (port->tty) {
|
||||
set_bit(TTY_IO_ERROR, &port->tty->flags);
|
||||
tty_kref_put(port->tty);
|
||||
}
|
||||
port->tty = NULL;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
wake_up_interruptible(&port->open_wait);
|
||||
wake_up_interruptible(&port->delta_msr_wait);
|
||||
tty_port_shutdown(port);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_hangup);
|
||||
|
||||
/**
|
||||
* tty_port_carrier_raised - carrier raised check
|
||||
* @port: tty port
|
||||
*
|
||||
* Wrapper for the carrier detect logic. For the moment this is used
|
||||
* to hide some internal details. This will eventually become entirely
|
||||
* internal to the tty port.
|
||||
*/
|
||||
|
||||
int tty_port_carrier_raised(struct tty_port *port)
|
||||
{
|
||||
if (port->ops->carrier_raised == NULL)
|
||||
return 1;
|
||||
return port->ops->carrier_raised(port);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_carrier_raised);
|
||||
|
||||
/**
|
||||
* tty_port_raise_dtr_rts - Raise DTR/RTS
|
||||
* @port: tty port
|
||||
*
|
||||
* Wrapper for the DTR/RTS raise logic. For the moment this is used
|
||||
* to hide some internal details. This will eventually become entirely
|
||||
* internal to the tty port.
|
||||
*/
|
||||
|
||||
void tty_port_raise_dtr_rts(struct tty_port *port)
|
||||
{
|
||||
if (port->ops->dtr_rts)
|
||||
port->ops->dtr_rts(port, 1);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_raise_dtr_rts);
|
||||
|
||||
/**
|
||||
* tty_port_lower_dtr_rts - Lower DTR/RTS
|
||||
* @port: tty port
|
||||
*
|
||||
* Wrapper for the DTR/RTS raise logic. For the moment this is used
|
||||
* to hide some internal details. This will eventually become entirely
|
||||
* internal to the tty port.
|
||||
*/
|
||||
|
||||
void tty_port_lower_dtr_rts(struct tty_port *port)
|
||||
{
|
||||
if (port->ops->dtr_rts)
|
||||
port->ops->dtr_rts(port, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_lower_dtr_rts);
|
||||
|
||||
/**
|
||||
* tty_port_block_til_ready - Waiting logic for tty open
|
||||
* @port: the tty port being opened
|
||||
* @tty: the tty device being bound
|
||||
* @filp: the file pointer of the opener
|
||||
*
|
||||
* Implement the core POSIX/SuS tty behaviour when opening a tty device.
|
||||
* Handles:
|
||||
* - hangup (both before and during)
|
||||
* - non blocking open
|
||||
* - rts/dtr/dcd
|
||||
* - signals
|
||||
* - port flags and counts
|
||||
*
|
||||
* The passed tty_port must implement the carrier_raised method if it can
|
||||
* do carrier detect and the dtr_rts method if it supports software
|
||||
* management of these lines. Note that the dtr/rts raise is done each
|
||||
* iteration as a hangup may have previously dropped them while we wait.
|
||||
*/
|
||||
|
||||
int tty_port_block_til_ready(struct tty_port *port,
|
||||
struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
int do_clocal = 0, retval;
|
||||
unsigned long flags;
|
||||
DEFINE_WAIT(wait);
|
||||
int cd;
|
||||
|
||||
/* block if port is in the process of being closed */
|
||||
if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
|
||||
wait_event_interruptible_tty(port->close_wait,
|
||||
!(port->flags & ASYNC_CLOSING));
|
||||
if (port->flags & ASYNC_HUP_NOTIFY)
|
||||
return -EAGAIN;
|
||||
else
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
|
||||
/* if non-blocking mode is set we can pass directly to open unless
|
||||
the port has just hung up or is in another error state */
|
||||
if (tty->flags & (1 << TTY_IO_ERROR)) {
|
||||
port->flags |= ASYNC_NORMAL_ACTIVE;
|
||||
return 0;
|
||||
}
|
||||
if (filp->f_flags & O_NONBLOCK) {
|
||||
/* Indicate we are open */
|
||||
if (tty->termios->c_cflag & CBAUD)
|
||||
tty_port_raise_dtr_rts(port);
|
||||
port->flags |= ASYNC_NORMAL_ACTIVE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (C_CLOCAL(tty))
|
||||
do_clocal = 1;
|
||||
|
||||
/* Block waiting until we can proceed. We may need to wait for the
|
||||
carrier, but we must also wait for any close that is in progress
|
||||
before the next open may complete */
|
||||
|
||||
retval = 0;
|
||||
|
||||
/* The port lock protects the port counts */
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
if (!tty_hung_up_p(filp))
|
||||
port->count--;
|
||||
port->blocked_open++;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
|
||||
while (1) {
|
||||
/* Indicate we are open */
|
||||
if (tty->termios->c_cflag & CBAUD)
|
||||
tty_port_raise_dtr_rts(port);
|
||||
|
||||
prepare_to_wait(&port->open_wait, &wait, TASK_INTERRUPTIBLE);
|
||||
/* Check for a hangup or uninitialised port.
|
||||
Return accordingly */
|
||||
if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) {
|
||||
if (port->flags & ASYNC_HUP_NOTIFY)
|
||||
retval = -EAGAIN;
|
||||
else
|
||||
retval = -ERESTARTSYS;
|
||||
break;
|
||||
}
|
||||
/* Probe the carrier. For devices with no carrier detect this
|
||||
will always return true */
|
||||
cd = tty_port_carrier_raised(port);
|
||||
if (!(port->flags & ASYNC_CLOSING) &&
|
||||
(do_clocal || cd))
|
||||
break;
|
||||
if (signal_pending(current)) {
|
||||
retval = -ERESTARTSYS;
|
||||
break;
|
||||
}
|
||||
tty_unlock();
|
||||
schedule();
|
||||
tty_lock();
|
||||
}
|
||||
finish_wait(&port->open_wait, &wait);
|
||||
|
||||
/* Update counts. A parallel hangup will have set count to zero and
|
||||
we must not mess that up further */
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
if (!tty_hung_up_p(filp))
|
||||
port->count++;
|
||||
port->blocked_open--;
|
||||
if (retval == 0)
|
||||
port->flags |= ASYNC_NORMAL_ACTIVE;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_block_til_ready);
|
||||
|
||||
int tty_port_close_start(struct tty_port *port,
|
||||
struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
if (tty_hung_up_p(filp)) {
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tty->count == 1 && port->count != 1) {
|
||||
printk(KERN_WARNING
|
||||
"tty_port_close_start: tty->count = 1 port count = %d.\n",
|
||||
port->count);
|
||||
port->count = 1;
|
||||
}
|
||||
if (--port->count < 0) {
|
||||
printk(KERN_WARNING "tty_port_close_start: count = %d\n",
|
||||
port->count);
|
||||
port->count = 0;
|
||||
}
|
||||
|
||||
if (port->count) {
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
if (port->ops->drop)
|
||||
port->ops->drop(port);
|
||||
return 0;
|
||||
}
|
||||
set_bit(ASYNCB_CLOSING, &port->flags);
|
||||
tty->closing = 1;
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
/* Don't block on a stalled port, just pull the chain */
|
||||
if (tty->flow_stopped)
|
||||
tty_driver_flush_buffer(tty);
|
||||
if (test_bit(ASYNCB_INITIALIZED, &port->flags) &&
|
||||
port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
|
||||
tty_wait_until_sent(tty, port->closing_wait);
|
||||
if (port->drain_delay) {
|
||||
unsigned int bps = tty_get_baud_rate(tty);
|
||||
long timeout;
|
||||
|
||||
if (bps > 1200)
|
||||
timeout = max_t(long,
|
||||
(HZ * 10 * port->drain_delay) / bps, HZ / 10);
|
||||
else
|
||||
timeout = 2 * HZ;
|
||||
schedule_timeout_interruptible(timeout);
|
||||
}
|
||||
/* Flush the ldisc buffering */
|
||||
tty_ldisc_flush(tty);
|
||||
|
||||
/* Drop DTR/RTS if HUPCL is set. This causes any attached modem to
|
||||
hang up the line */
|
||||
if (tty->termios->c_cflag & HUPCL)
|
||||
tty_port_lower_dtr_rts(port);
|
||||
|
||||
/* Don't call port->drop for the last reference. Callers will want
|
||||
to drop the last active reference in ->shutdown() or the tty
|
||||
shutdown path */
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_close_start);
|
||||
|
||||
void tty_port_close_end(struct tty_port *port, struct tty_struct *tty)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
tty->closing = 0;
|
||||
|
||||
if (port->blocked_open) {
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
if (port->close_delay) {
|
||||
msleep_interruptible(
|
||||
jiffies_to_msecs(port->close_delay));
|
||||
}
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
wake_up_interruptible(&port->open_wait);
|
||||
}
|
||||
port->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING);
|
||||
wake_up_interruptible(&port->close_wait);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_close_end);
|
||||
|
||||
void tty_port_close(struct tty_port *port, struct tty_struct *tty,
|
||||
struct file *filp)
|
||||
{
|
||||
if (tty_port_close_start(port, tty, filp) == 0)
|
||||
return;
|
||||
tty_port_shutdown(port);
|
||||
set_bit(TTY_IO_ERROR, &tty->flags);
|
||||
tty_port_close_end(port, tty);
|
||||
tty_port_tty_set(port, NULL);
|
||||
}
|
||||
EXPORT_SYMBOL(tty_port_close);
|
||||
|
||||
int tty_port_open(struct tty_port *port, struct tty_struct *tty,
|
||||
struct file *filp)
|
||||
{
|
||||
spin_lock_irq(&port->lock);
|
||||
if (!tty_hung_up_p(filp))
|
||||
++port->count;
|
||||
spin_unlock_irq(&port->lock);
|
||||
tty_port_tty_set(port, tty);
|
||||
|
||||
/*
|
||||
* Do the device-specific open only if the hardware isn't
|
||||
* already initialized. Serialize open and shutdown using the
|
||||
* port mutex.
|
||||
*/
|
||||
|
||||
mutex_lock(&port->mutex);
|
||||
|
||||
if (!test_bit(ASYNCB_INITIALIZED, &port->flags)) {
|
||||
clear_bit(TTY_IO_ERROR, &tty->flags);
|
||||
if (port->ops->activate) {
|
||||
int retval = port->ops->activate(port, tty);
|
||||
if (retval) {
|
||||
mutex_unlock(&port->mutex);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
set_bit(ASYNCB_INITIALIZED, &port->flags);
|
||||
}
|
||||
mutex_unlock(&port->mutex);
|
||||
return tty_port_block_til_ready(port, tty, filp);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(tty_port_open);
|
Reference in New Issue
Block a user