diff options
author | Greg Kroah-Hartman <gregkh@suse.de> | 2010-11-04 11:10:29 -0700 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-11-05 08:10:33 -0700 |
commit | 96fd7ce58ffb5c7bf376796b5525ba3ea1c9d69f (patch) | |
tree | aca24a6c1c0e506d5fa7b0266c4c1866786607ae /drivers/tty | |
parent | c8ddb2713c624f432fa5fe3c7ecffcdda46ea0d4 (diff) | |
download | blackbird-op-linux-96fd7ce58ffb5c7bf376796b5525ba3ea1c9d69f.tar.gz blackbird-op-linux-96fd7ce58ffb5c7bf376796b5525ba3ea1c9d69f.zip |
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>
Diffstat (limited to 'drivers/tty')
-rw-r--r-- | drivers/tty/Makefile | 9 | ||||
-rw-r--r-- | drivers/tty/n_gsm.c | 2763 | ||||
-rw-r--r-- | drivers/tty/n_hdlc.c | 1007 | ||||
-rw-r--r-- | drivers/tty/n_r3964.c | 1264 | ||||
-rw-r--r-- | drivers/tty/n_tty.c | 2121 | ||||
-rw-r--r-- | drivers/tty/pty.c | 777 | ||||
-rw-r--r-- | drivers/tty/sysrq.c | 811 | ||||
-rw-r--r-- | drivers/tty/tty_audit.c | 358 | ||||
-rw-r--r-- | drivers/tty/tty_buffer.c | 524 | ||||
-rw-r--r-- | drivers/tty/tty_io.c | 3263 | ||||
-rw-r--r-- | drivers/tty/tty_ioctl.c | 1179 | ||||
-rw-r--r-- | drivers/tty/tty_ldisc.c | 915 | ||||
-rw-r--r-- | drivers/tty/tty_mutex.c | 47 | ||||
-rw-r--r-- | drivers/tty/tty_port.c | 446 |
14 files changed, 15484 insertions, 0 deletions
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile new file mode 100644 index 000000000000..7f63b3315e82 --- /dev/null +++ b/drivers/tty/Makefile @@ -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 diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c new file mode 100644 index 000000000000..04ef3ef0a422 --- /dev/null +++ b/drivers/tty/n_gsm.c @@ -0,0 +1,2763 @@ +/* + * n_gsm.c GSM 0710 tty multiplexor + * Copyright (c) 2009/10 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * * THIS IS A DEVELOPMENT SNAPSHOT IT IS NOT A FINAL RELEASE * + * + * TO DO: + * Mostly done: ioctls for setting modes/timing + * Partly done: hooks so you can pull off frames to non tty devs + * Restart DLCI 0 when it closes ? + * Test basic encoding + * Improve the tx engine + * Resolve tx side locking by adding a queue_head and routing + * all control traffic via it + * General tidy/document + * Review the locking/move to refcounts more (mux now moved to an + * alloc/free model ready) + * Use newest tty open/close port helpers and install hooks + * What to do about power functions ? + * Termios setting and negotiation + * Do we need a 'which mux are you' ioctl to correlate mux and tty sets + * + */ + +#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/ctype.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/bitops.h> +#include <linux/file.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/timer.h> +#include <linux/tty_flip.h> +#include <linux/tty_driver.h> +#include <linux/serial.h> +#include <linux/kfifo.h> +#include <linux/skbuff.h> +#include <linux/gsmmux.h> + +static int debug; +module_param(debug, int, 0600); + +#define T1 (HZ/10) +#define T2 (HZ/3) +#define N2 3 + +/* Use long timers for testing at low speed with debug on */ +#ifdef DEBUG_TIMING +#define T1 HZ +#define T2 (2 * HZ) +#endif + +/* Semi-arbitary buffer size limits. 0710 is normally run with 32-64 byte + limits so this is plenty */ +#define MAX_MRU 512 +#define MAX_MTU 512 + +/* + * Each block of data we have queued to go out is in the form of + * a gsm_msg which holds everything we need in a link layer independant + * format + */ + +struct gsm_msg { + struct gsm_msg *next; + u8 addr; /* DLCI address + flags */ + u8 ctrl; /* Control byte + flags */ + unsigned int len; /* Length of data block (can be zero) */ + unsigned char *data; /* Points into buffer but not at the start */ + unsigned char buffer[0]; +}; + +/* + * Each active data link has a gsm_dlci structure associated which ties + * the link layer to an optional tty (if the tty side is open). To avoid + * complexity right now these are only ever freed up when the mux is + * shut down. + * + * At the moment we don't free DLCI objects until the mux is torn down + * this avoid object life time issues but might be worth review later. + */ + +struct gsm_dlci { + struct gsm_mux *gsm; + int addr; + int state; +#define DLCI_CLOSED 0 +#define DLCI_OPENING 1 /* Sending SABM not seen UA */ +#define DLCI_OPEN 2 /* SABM/UA complete */ +#define DLCI_CLOSING 3 /* Sending DISC not seen UA/DM */ + + /* Link layer */ + spinlock_t lock; /* Protects the internal state */ + struct timer_list t1; /* Retransmit timer for SABM and UA */ + int retries; + /* Uplink tty if active */ + struct tty_port port; /* The tty bound to this DLCI if there is one */ + struct kfifo *fifo; /* Queue fifo for the DLCI */ + struct kfifo _fifo; /* For new fifo API porting only */ + int adaption; /* Adaption layer in use */ + u32 modem_rx; /* Our incoming virtual modem lines */ + u32 modem_tx; /* Our outgoing modem lines */ + int dead; /* Refuse re-open */ + /* Flow control */ + int throttled; /* Private copy of throttle state */ + int constipated; /* Throttle status for outgoing */ + /* Packetised I/O */ + struct sk_buff *skb; /* Frame being sent */ + struct sk_buff_head skb_list; /* Queued frames */ + /* Data handling callback */ + void (*data)(struct gsm_dlci *dlci, u8 *data, int len); +}; + +/* DLCI 0, 62/63 are special or reseved see gsmtty_open */ + +#define NUM_DLCI 64 + +/* + * DLCI 0 is used to pass control blocks out of band of the data + * flow (and with a higher link priority). One command can be outstanding + * at a time and we use this structure to manage them. They are created + * and destroyed by the user context, and updated by the receive paths + * and timers + */ + +struct gsm_control { + u8 cmd; /* Command we are issuing */ + u8 *data; /* Data for the command in case we retransmit */ + int len; /* Length of block for retransmission */ + int done; /* Done flag */ + int error; /* Error if any */ +}; + +/* + * Each GSM mux we have is represented by this structure. If we are + * operating as an ldisc then we use this structure as our ldisc + * state. We need to sort out lifetimes and locking with respect + * to the gsm mux array. For now we don't free DLCI objects that + * have been instantiated until the mux itself is terminated. + * + * To consider further: tty open versus mux shutdown. + */ + +struct gsm_mux { + struct tty_struct *tty; /* The tty our ldisc is bound to */ + spinlock_t lock; + + /* Events on the GSM channel */ + wait_queue_head_t event; + + /* Bits for GSM mode decoding */ + + /* Framing Layer */ + unsigned char *buf; + int state; +#define GSM_SEARCH 0 +#define GSM_START 1 +#define GSM_ADDRESS 2 +#define GSM_CONTROL 3 +#define GSM_LEN 4 +#define GSM_DATA 5 +#define GSM_FCS 6 +#define GSM_OVERRUN 7 + unsigned int len; + unsigned int address; + unsigned int count; + int escape; + int encoding; + u8 control; + u8 fcs; + u8 *txframe; /* TX framing buffer */ + + /* Methods for the receiver side */ + void (*receive)(struct gsm_mux *gsm, u8 ch); + void (*error)(struct gsm_mux *gsm, u8 ch, u8 flag); + /* And transmit side */ + int (*output)(struct gsm_mux *mux, u8 *data, int len); + + /* Link Layer */ + unsigned int mru; + unsigned int mtu; + int initiator; /* Did we initiate connection */ + int dead; /* Has the mux been shut down */ + struct gsm_dlci *dlci[NUM_DLCI]; + int constipated; /* Asked by remote to shut up */ + + spinlock_t tx_lock; + unsigned int tx_bytes; /* TX data outstanding */ +#define TX_THRESH_HI 8192 +#define TX_THRESH_LO 2048 + struct gsm_msg *tx_head; /* Pending data packets */ + struct gsm_msg *tx_tail; + + /* Control messages */ + struct timer_list t2_timer; /* Retransmit timer for commands */ + int cretries; /* Command retry counter */ + struct gsm_control *pending_cmd;/* Our current pending command */ + spinlock_t control_lock; /* Protects the pending command */ + + /* Configuration */ + int adaption; /* 1 or 2 supported */ + u8 ftype; /* UI or UIH */ + int t1, t2; /* Timers in 1/100th of a sec */ + int n2; /* Retry count */ + + /* Statistics (not currently exposed) */ + unsigned long bad_fcs; + unsigned long malformed; + unsigned long io_error; + unsigned long bad_size; + unsigned long unsupported; +}; + + +/* + * Mux objects - needed so that we can translate a tty index into the + * relevant mux and DLCI. + */ + +#define MAX_MUX 4 /* 256 minors */ +static struct gsm_mux *gsm_mux[MAX_MUX]; /* GSM muxes */ +static spinlock_t gsm_mux_lock; + +/* + * This section of the driver logic implements the GSM encodings + * both the basic and the 'advanced'. Reliable transport is not + * supported. + */ + +#define CR 0x02 +#define EA 0x01 +#define PF 0x10 + +/* I is special: the rest are ..*/ +#define RR 0x01 +#define UI 0x03 +#define RNR 0x05 +#define REJ 0x09 +#define DM 0x0F +#define SABM 0x2F +#define DISC 0x43 +#define UA 0x63 +#define UIH 0xEF + +/* Channel commands */ +#define CMD_NSC 0x09 +#define CMD_TEST 0x11 +#define CMD_PSC 0x21 +#define CMD_RLS 0x29 +#define CMD_FCOFF 0x31 +#define CMD_PN 0x41 +#define CMD_RPN 0x49 +#define CMD_FCON 0x51 +#define CMD_CLD 0x61 +#define CMD_SNC 0x69 +#define CMD_MSC 0x71 + +/* Virtual modem bits */ +#define MDM_FC 0x01 +#define MDM_RTC 0x02 +#define MDM_RTR 0x04 +#define MDM_IC 0x20 +#define MDM_DV 0x40 + +#define GSM0_SOF 0xF9 +#define GSM1_SOF 0x7E +#define GSM1_ESCAPE 0x7D +#define GSM1_ESCAPE_BITS 0x20 +#define XON 0x11 +#define XOFF 0x13 + +static const struct tty_port_operations gsm_port_ops; + +/* + * CRC table for GSM 0710 + */ + +static const u8 gsm_fcs8[256] = { + 0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75, + 0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B, + 0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69, + 0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67, + 0x38, 0xA9, 0xDB, 0x4A, 0x3F, 0xAE, 0xDC, 0x4D, + 0x36, 0xA7, 0xD5, 0x44, 0x31, 0xA0, 0xD2, 0x43, + 0x24, 0xB5, 0xC7, 0x56, 0x23, 0xB2, 0xC0, 0x51, + 0x2A, 0xBB, 0xC9, 0x58, 0x2D, 0xBC, 0xCE, 0x5F, + 0x70, 0xE1, 0x93, 0x02, 0x77, 0xE6, 0x94, 0x05, + 0x7E, 0xEF, 0x9D, 0x0C, 0x79, 0xE8, 0x9A, 0x0B, + 0x6C, 0xFD, 0x8F, 0x1E, 0x6B, 0xFA, 0x88, 0x19, + 0x62, 0xF3, 0x81, 0x10, 0x65, 0xF4, 0x86, 0x17, + 0x48, 0xD9, 0xAB, 0x3A, 0x4F, 0xDE, 0xAC, 0x3D, + 0x46, 0xD7, 0xA5, 0x34, 0x41, 0xD0, 0xA2, 0x33, + 0x54, 0xC5, 0xB7, 0x26, 0x53, 0xC2, 0xB0, 0x21, + 0x5A, 0xCB, 0xB9, 0x28, 0x5D, 0xCC, 0xBE, 0x2F, + 0xE0, 0x71, 0x03, 0x92, 0xE7, 0x76, 0x04, 0x95, + 0xEE, 0x7F, 0x0D, 0x9C, 0xE9, 0x78, 0x0A, 0x9B, + 0xFC, 0x6D, 0x1F, 0x8E, 0xFB, 0x6A, 0x18, 0x89, + 0xF2, 0x63, 0x11, 0x80, 0xF5, 0x64, 0x16, 0x87, + 0xD8, 0x49, 0x3B, 0xAA, 0xDF, 0x4E, 0x3C, 0xAD, + 0xD6, 0x47, 0x35, 0xA4, 0xD1, 0x40, 0x32, 0xA3, + 0xC4, 0x55, 0x27, 0xB6, 0xC3, 0x52, 0x20, 0xB1, + 0xCA, 0x5B, 0x29, 0xB8, 0xCD, 0x5C, 0x2E, 0xBF, + 0x90, 0x01, 0x73, 0xE2, 0x97, 0x06, 0x74, 0xE5, + 0x9E, 0x0F, 0x7D, 0xEC, 0x99, 0x08, 0x7A, 0xEB, + 0x8C, 0x1D, 0x6F, 0xFE, 0x8B, 0x1A, 0x68, 0xF9, + 0x82, 0x13, 0x61, 0xF0, 0x85, 0x14, 0x66, 0xF7, + 0xA8, 0x39, 0x4B, 0xDA, 0xAF, 0x3E, 0x4C, 0xDD, + 0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3, + 0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1, + 0xBA, 0x2B, 0x59, 0xC8, 0xBD, 0x2C, 0x5E, 0xCF +}; + +#define INIT_FCS 0xFF +#define GOOD_FCS 0xCF + +/** + * gsm_fcs_add - update FCS + * @fcs: Current FCS + * @c: Next data + * + * Update the FCS to include c. Uses the algorithm in the specification + * notes. + */ + +static inline u8 gsm_fcs_add(u8 fcs, u8 c) +{ + return gsm_fcs8[fcs ^ c]; +} + +/** + * gsm_fcs_add_block - update FCS for a block + * @fcs: Current FCS + * @c: buffer of data + * @len: length of buffer + * + * Update the FCS to include c. Uses the algorithm in the specification + * notes. + */ + +static inline u8 gsm_fcs_add_block(u8 fcs, u8 *c, int len) +{ + while (len--) + fcs = gsm_fcs8[fcs ^ *c++]; + return fcs; +} + +/** + * gsm_read_ea - read a byte into an EA + * @val: variable holding value + * c: byte going into the EA + * + * Processes one byte of an EA. Updates the passed variable + * and returns 1 if the EA is now completely read + */ + +static int gsm_read_ea(unsigned int *val, u8 c) +{ + /* Add the next 7 bits into the value */ + *val <<= 7; + *val |= c >> 1; + /* Was this the last byte of the EA 1 = yes*/ + return c & EA; +} + +/** + * gsm_encode_modem - encode modem data bits + * @dlci: DLCI to encode from + * + * Returns the correct GSM encoded modem status bits (6 bit field) for + * the current status of the DLCI and attached tty object + */ + +static u8 gsm_encode_modem(const struct gsm_dlci *dlci) +{ + u8 modembits = 0; + /* FC is true flow control not modem bits */ + if (dlci->throttled) + modembits |= MDM_FC; + if (dlci->modem_tx & TIOCM_DTR) + modembits |= MDM_RTC; + if (dlci->modem_tx & TIOCM_RTS) + modembits |= MDM_RTR; + if (dlci->modem_tx & TIOCM_RI) + modembits |= MDM_IC; + if (dlci->modem_tx & TIOCM_CD) + modembits |= MDM_DV; + return modembits; +} + +/** + * gsm_print_packet - display a frame for debug + * @hdr: header to print before decode + * @addr: address EA from the frame + * @cr: C/R bit from the frame + * @control: control including PF bit + * @data: following data bytes + * @dlen: length of data + * + * Displays a packet in human readable format for debugging purposes. The + * style is based on amateur radio LAP-B dump display. + */ + +static void gsm_print_packet(const char *hdr, int addr, int cr, + u8 control, const u8 *data, int dlen) +{ + if (!(debug & 1)) + return; + + printk(KERN_INFO "%s %d) %c: ", hdr, addr, "RC"[cr]); + + switch (control & ~PF) { + case SABM: + printk(KERN_CONT "SABM"); + break; + case UA: + printk(KERN_CONT "UA"); + break; + case DISC: + printk(KERN_CONT "DISC"); + break; + case DM: + printk(KERN_CONT "DM"); + break; + case UI: + printk(KERN_CONT "UI"); + break; + case UIH: + printk(KERN_CONT "UIH"); + break; + default: + if (!(control & 0x01)) { + printk(KERN_CONT "I N(S)%d N(R)%d", + (control & 0x0E) >> 1, (control & 0xE)>> 5); + } else switch (control & 0x0F) { + case RR: + printk("RR(%d)", (control & 0xE0) >> 5); + break; + case RNR: + printk("RNR(%d)", (control & 0xE0) >> 5); + break; + case REJ: + printk("REJ(%d)", (control & 0xE0) >> 5); + break; + default: + printk(KERN_CONT "[%02X]", control); + } + } + + if (control & PF) + printk(KERN_CONT "(P)"); + else + printk(KERN_CONT "(F)"); + + if (dlen) { + int ct = 0; + while (dlen--) { + if (ct % 8 == 0) + printk(KERN_CONT "\n "); + printk(KERN_CONT "%02X ", *data++); + ct++; + } + } + printk(KERN_CONT "\n"); +} + + +/* + * Link level transmission side + */ + +/** + * gsm_stuff_packet - bytestuff a packet + * @ibuf: input + * @obuf: output + * @len: length of input + * + * Expand a buffer by bytestuffing it. The worst case size change + * is doubling and the caller is responsible for handing out + * suitable sized buffers. + */ + +static int gsm_stuff_frame(const u8 *input, u8 *output, int len) +{ + int olen = 0; + while (len--) { + if (*input == GSM1_SOF || *input == GSM1_ESCAPE + || *input == XON || *input == XOFF) { + *output++ = GSM1_ESCAPE; + *output++ = *input++ ^ GSM1_ESCAPE_BITS; + olen++; + } else + *output++ = *input++; + olen++; + } + return olen; +} + +static void hex_packet(const unsigned char *p, int len) +{ + int i; + for (i = 0; i < len; i++) { + if (i && (i % 16) == 0) + printk("\n"); + printk("%02X ", *p++); + } + printk("\n"); +} + +/** + * gsm_send - send a control frame + * @gsm: our GSM mux + * @addr: address for control frame + * @cr: command/response bit + * @control: control byte including PF bit + * + * Format up and transmit a control frame. These do not go via the + * queueing logic as they should be transmitted ahead of data when + * they are needed. + * + * FIXME: Lock versus data TX path + */ + +static void gsm_send(struct gsm_mux *gsm, int addr, int cr, int control) +{ + int len; + u8 cbuf[10]; + u8 ibuf[3]; + + switch (gsm->encoding) { + case 0: + cbuf[0] = GSM0_SOF; + cbuf[1] = (addr << 2) | (cr << 1) | EA; + cbuf[2] = control; + cbuf[3] = EA; /* Length of data = 0 */ + cbuf[4] = 0xFF - gsm_fcs_add_block(INIT_FCS, cbuf + 1, 3); + cbuf[5] = GSM0_SOF; + len = 6; + break; + case 1: + case 2: + /* Control frame + packing (but not frame stuffing) in mode 1 */ + ibuf[0] = (addr << 2) | (cr << 1) | EA; + ibuf[1] = control; + ibuf[2] = 0xFF - gsm_fcs_add_block(INIT_FCS, ibuf, 2); + /* Stuffing may double the size worst case */ + len = gsm_stuff_frame(ibuf, cbuf + 1, 3); + /* Now add the SOF markers */ + cbuf[0] = GSM1_SOF; + cbuf[len + 1] = GSM1_SOF; + /* FIXME: we can omit the lead one in many cases */ + len += 2; + break; + default: + WARN_ON(1); + return; + } + gsm->output(gsm, cbuf, len); + gsm_print_packet("-->", addr, cr, control, NULL, 0); +} + +/** + * gsm_response - send a control response + * @gsm: our GSM mux + * @addr: address for control frame + * @control: control byte including PF bit + * + * Format up and transmit a link level response frame. + */ + +static inline void gsm_response(struct gsm_mux *gsm, int addr, int control) +{ + gsm_send(gsm, addr, 0, control); +} + +/** + * gsm_command - send a control command + * @gsm: our GSM mux + * @addr: address for control frame + * @control: control byte including PF bit + * + * Format up and transmit a link level command frame. + */ + +static inline void gsm_command(struct gsm_mux *gsm, int addr, int control) +{ + gsm_send(gsm, addr, 1, control); +} + +/* Data transmission */ + +#define HDR_LEN 6 /* ADDR CTRL [LEN.2] DATA FCS */ + +/** + * gsm_data_alloc - allocate data frame + * @gsm: GSM mux + * @addr: DLCI address + * @len: length excluding header and FCS + * @ctrl: control byte + * + * Allocate a new data buffer for sending frames with data. Space is left + * at the front for header bytes but that is treated as an implementation + * detail and not for the high level code to use + */ + +static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len, + u8 ctrl) +{ + struct gsm_msg *m = kmalloc(sizeof(struct gsm_msg) + len + HDR_LEN, + GFP_ATOMIC); + if (m == NULL) + return NULL; + m->data = m->buffer + HDR_LEN - 1; /* Allow for FCS */ + m->len = len; + m->addr = addr; + m->ctrl = ctrl; + m->next = NULL; + return m; +} + +/** + * gsm_data_kick - poke the queue + * @gsm: GSM Mux + * + * The tty device has called us to indicate that room has appeared in + * the transmit queue. Ram more data into the pipe if we have any + * + * FIXME: lock against link layer control transmissions + */ + +static void gsm_data_kick(struct gsm_mux *gsm) +{ + struct gsm_msg *msg = gsm->tx_head; + int len; + int skip_sof = 0; + + /* FIXME: We need to apply this solely to data messages */ + if (gsm->constipated) + return; + + while (gsm->tx_head != NULL) { + msg = gsm->tx_head; + if (gsm->encoding != 0) { + gsm->txframe[0] = GSM1_SOF; + len = gsm_stuff_frame(msg->data, + gsm->txframe + 1, msg->len); + gsm->txframe[len + 1] = GSM1_SOF; + len += 2; + } else { + gsm->txframe[0] = GSM0_SOF; + memcpy(gsm->txframe + 1 , msg->data, msg->len); + gsm->txframe[msg->len + 1] = GSM0_SOF; + len = msg->len + 2; + } + + if (debug & 4) { + printk("gsm_data_kick: \n"); + hex_packet(gsm->txframe, len); + } + + if (gsm->output(gsm, gsm->txframe + skip_sof, + len - skip_sof) < 0) + break; + /* FIXME: Can eliminate one SOF in many more cases */ + gsm->tx_head = msg->next; + if (gsm->tx_head == NULL) + gsm->tx_tail = NULL; + gsm->tx_bytes -= msg->len; + kfree(msg); + /* For a burst of frames skip the extra SOF within the + burst */ + skip_sof = 1; + } +} + +/** + * __gsm_data_queue - queue a UI or UIH frame + * @dlci: DLCI sending the data + * @msg: message queued + * + * Add data to the transmit queue and try and get stuff moving + * out of the mux tty if not already doing so. The Caller must hold + * the gsm tx lock. + */ + +static void __gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg) +{ + struct gsm_mux *gsm = dlci->gsm; + u8 *dp = msg->data; + u8 *fcs = dp + msg->len; + + /* Fill in the header */ + if (gsm->encoding == 0) { + if (msg->len < 128) + *--dp = (msg->len << 1) | EA; + else { + *--dp = (msg->len >> 6) | EA; + *--dp = (msg->len & 127) << 1; + } + } + + *--dp = msg->ctrl; + if (gsm->initiator) + *--dp = (msg->addr << 2) | 2 | EA; + else + *--dp = (msg->addr << 2) | EA; + *fcs = gsm_fcs_add_block(INIT_FCS, dp , msg->data - dp); + /* Ugly protocol layering violation */ + if (msg->ctrl == UI || msg->ctrl == (UI|PF)) + *fcs = gsm_fcs_add_block(*fcs, msg->data, msg->len); + *fcs = 0xFF - *fcs; + + gsm_print_packet("Q> ", msg->addr, gsm->initiator, msg->ctrl, + msg->data, msg->len); + + /* Move the header back and adjust the length, also allow for the FCS + now tacked on the end */ + msg->len += (msg->data - dp) + 1; + msg->data = dp; + + /* Add to the actual output queue */ + if (gsm->tx_tail) + gsm->tx_tail->next = msg; + else + gsm->tx_head = msg; + gsm->tx_tail = msg; + gsm->tx_bytes += msg->len; + gsm_data_kick(gsm); +} + +/** + * gsm_data_queue - queue a UI or UIH frame + * @dlci: DLCI sending the data + * @msg: message queued + * + * Add data to the transmit queue and try and get stuff moving + * out of the mux tty if not already doing so. Take the + * the gsm tx lock and dlci lock. + */ + +static void gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg) +{ + unsigned long flags; + spin_lock_irqsave(&dlci->gsm->tx_lock, flags); + __gsm_data_queue(dlci, msg); + spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags); +} + +/** + * gsm_dlci_data_output - try and push data out of a DLCI + * @gsm: mux + * @dlci: the DLCI to pull data from + * + * Pull data from a DLCI and send it into the transmit queue if there + * is data. Keep to the MRU of the mux. This path handles the usual tty + * interface which is a byte stream with optional modem data. + * + * Caller must hold the tx_lock of the mux. + */ + +static int gsm_dlci_data_output(struct gsm_mux *gsm, struct gsm_dlci *dlci) +{ + struct gsm_msg *msg; + u8 *dp; + int len, size; + int h = dlci->adaption - 1; + + len = kfifo_len(dlci->fifo); + if (len == 0) + return 0; + + /* MTU/MRU count only the data bits */ + if (len > gsm->mtu) + len = gsm->mtu; + + size = len + h; + + msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype); + /* FIXME: need a timer or something to kick this so it can't + get stuck with no work outstanding and no buffer free */ + if (msg == NULL) + return -ENOMEM; + dp = msg->data; + switch (dlci->adaption) { + case 1: /* Unstructured */ + break; + case 2: /* Unstructed with modem bits. Always one byte as we never + send inline break data */ + *dp += gsm_encode_modem(dlci); + len--; + break; + } + WARN_ON(kfifo_out_locked(dlci->fifo, dp , len, &dlci->lock) != len); + __gsm_data_queue(dlci, msg); + /* Bytes of data we used up */ + return size; +} + +/** + * gsm_dlci_data_output_framed - try and push data out of a DLCI + * @gsm: mux + * @dlci: the DLCI to pull data from + * + * Pull data from a DLCI and send it into the transmit queue if there + * is data. Keep to the MRU of the mux. This path handles framed data + * queued as skbuffs to the DLCI. + * + * Caller must hold the tx_lock of the mux. + */ + +static int gsm_dlci_data_output_framed(struct gsm_mux *gsm, + struct gsm_dlci *dlci) +{ + struct gsm_msg *msg; + u8 *dp; + int len, size; + int last = 0, first = 0; + int overhead = 0; + + /* One byte per frame is used for B/F flags */ + if (dlci->adaption == 4) + overhead = 1; + + /* dlci->skb is locked by tx_lock */ + if (dlci->skb == NULL) { + dlci->skb = skb_dequeue(&dlci->skb_list); + if (dlci->skb == NULL) + return 0; + first = 1; + } + len = dlci->skb->len + overhead; + + /* MTU/MRU count only the data bits */ + if (len > gsm->mtu) { + if (dlci->adaption == 3) { + /* Over long frame, bin it */ + kfree_skb(dlci->skb); + dlci->skb = NULL; + return 0; + } + len = gsm->mtu; + } else + last = 1; + + size = len + overhead; + msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype); + + /* FIXME: need a timer or something to kick this so it can't + get stuck with no work outstanding and no buffer free */ + if (msg == NULL) + return -ENOMEM; + dp = msg->data; + + if (dlci->adaption == 4) { /* Interruptible framed (Packetised Data) */ + /* Flag byte to carry the start/end info */ + *dp++ = last << 7 | first << 6 | 1; /* EA */ + len--; + } + memcpy(dp, skb_pull(dlci->skb, len), len); + __gsm_data_queue(dlci, msg); + if (last) + dlci->skb = NULL; + return size; +} + +/** + * gsm_dlci_data_sweep - look for data to send + * @gsm: the GSM mux + * + * Sweep the GSM mux channels in priority order looking for ones with + * data to send. We could do with optimising this scan a bit. We aim + * to fill the queue totally or up to TX_THRESH_HI bytes. Once we hit + * TX_THRESH_LO we get called again + * + * FIXME: We should round robin between groups and in theory you can + * renegotiate DLCI priorities with optional stuff. Needs optimising. + */ + +static void gsm_dlci_data_sweep(struct gsm_mux *gsm) +{ + int len; + /* Priority ordering: We should do priority with RR of the groups */ + int i = 1; + + while (i < NUM_DLCI) { + struct gsm_dlci *dlci; + + if (gsm->tx_bytes > TX_THRESH_HI) + break; + dlci = gsm->dlci[i]; + if (dlci == NULL || dlci->constipated) { + i++; + continue; + } + if (dlci->adaption < 3) + len = gsm_dlci_data_output(gsm, dlci); + else + len = gsm_dlci_data_output_framed(gsm, dlci); + if (len < 0) + break; + /* DLCI empty - try the next */ + if (len == 0) + i++; + } +} + +/** + * gsm_dlci_data_kick - transmit if possible + * @dlci: DLCI to kick + * + * Transmit data from this DLCI if the queue is empty. We can't rely on + * a tty wakeup except when we filled the pipe so we need to fire off + * new data ourselves in other cases. + */ + +static void gsm_dlci_data_kick(struct gsm_dlci *dlci) +{ + unsigned long flags; + + spin_lock_irqsave(&dlci->gsm->tx_lock, flags); + /* If we have nothing running then we need to fire up */ + if (dlci->gsm->tx_bytes == 0) + gsm_dlci_data_output(dlci->gsm, dlci); + else if (dlci->gsm->tx_bytes < TX_THRESH_LO) + gsm_dlci_data_sweep(dlci->gsm); + spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags); +} + +/* + * Control message processing + */ + + +/** + * gsm_control_reply - send a response frame to a control + * @gsm: gsm channel + * @cmd: the command to use + * @data: data to follow encoded info + * @dlen: length of data + * + * Encode up and queue a UI/UIH frame containing our response. + */ + +static void gsm_control_reply(struct gsm_mux *gsm, int cmd, u8 *data, + int dlen) +{ + struct gsm_msg *msg; + msg = gsm_data_alloc(gsm, 0, dlen + 2, gsm->ftype); + msg->data[0] = (cmd & 0xFE) << 1 | EA; /* Clear C/R */ + msg->data[1] = (dlen << 1) | EA; + memcpy(msg->data + 2, data, dlen); + gsm_data_queue(gsm->dlci[0], msg); +} + +/** + * gsm_process_modem - process received modem status + * @tty: virtual tty bound to the DLCI + * @dlci: DLCI to affect + * @modem: modem bits (full EA) + * + * Used when a modem control message or line state inline in adaption + * layer 2 is processed. Sort out the local modem state and throttles + */ + +static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci, + u32 modem) +{ + int mlines = 0; + u8 brk = modem >> 6; + + /* Flow control/ready to communicate */ + if (modem & MDM_FC) { + /* Need to throttle our output on this device */ + dlci->constipated = 1; + } + if (modem & MDM_RTC) { + mlines |= TIOCM_DSR | TIOCM_DTR; + dlci->constipated = 0; + gsm_dlci_data_kick(dlci); + } + /* Map modem bits */ + if (modem & MDM_RTR) + mlines |= TIOCM_RTS | TIOCM_CTS; + if (modem & MDM_IC) + mlines |= TIOCM_RI; + if (modem & MDM_DV) + mlines |= TIOCM_CD; + + /* Carrier drop -> hangup */ + if (tty) { + if ((mlines & TIOCM_CD) == 0 && (dlci->modem_rx & TIOCM_CD)) + if (!(tty->termios->c_cflag & CLOCAL)) + tty_hangup(tty); + if (brk & 0x01) + tty_insert_flip_char(tty, 0, TTY_BREAK); + } + dlci->modem_rx = mlines; +} + +/** + * gsm_control_modem - modem status received + * @gsm: GSM channel + * @data: data following command + * @clen: command length + * + * We have received a modem status control message. This is used by + * the GSM mux protocol to pass virtual modem line status and optionally + * to indicate break signals. Unpack it, convert to Linux representation + * and if need be stuff a break message down the tty. + */ + +static void gsm_control_modem(struct gsm_mux *gsm, u8 *data, int clen) +{ + unsigned int addr = 0; + unsigned int modem = 0; + struct gsm_dlci *dlci; + int len = clen; + u8 *dp = data; + struct tty_struct *tty; + + while (gsm_read_ea(&addr, *dp++) == 0) { + len--; + if (len == 0) + return; + } + /* Must be at least one byte following the EA */ + len--; + if (len <= 0) + return; + + addr >>= 1; + /* Closed port, or invalid ? */ + if (addr == 0 || addr >= NUM_DLCI || gsm->dlci[addr] == NULL) + return; + dlci = gsm->dlci[addr]; + + while (gsm_read_ea(&modem, *dp++) == 0) { + len--; + if (len == 0) + return; + } + tty = tty_port_tty_get(&dlci->port); + gsm_process_modem(tty, dlci, modem); + if (tty) { + tty_wakeup(tty); + tty_kref_put(tty); + } + gsm_control_reply(gsm, CMD_MSC, data, clen); +} + +/** + * gsm_control_rls - remote line status + * @gsm: GSM channel + * @data: data bytes + * @clen: data length + * + * The modem sends us a two byte message on the control channel whenever + * it wishes to send us an error state from the virtual link. Stuff + * this into the uplink tty if present + */ + +static void gsm_control_rls(struct gsm_mux *gsm, u8 *data, int clen) +{ + struct tty_struct *tty; + unsigned int addr = 0 ; + u8 bits; + int len = clen; + u8 *dp = data; + + while (gsm_read_ea(&addr, *dp++) == 0) { + len--; + if (len == 0) + return; + } + /* Must be at least one byte following ea */ + len--; + if (len <= 0) + return; + addr >>= 1; + /* Closed port, or invalid ? */ + if (addr == 0 || addr >= NUM_DLCI || gsm->dlci[addr] == NULL) + return; + /* No error ? */ + bits = *dp; + if ((bits & 1) == 0) + return; + /* See if we have an uplink tty */ + tty = tty_port_tty_get(&gsm->dlci[addr]->port); + + if (tty) { + if (bits & 2) + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + if (bits & 4) + tty_insert_flip_char(tty, 0, TTY_PARITY); + if (bits & 8) + tty_insert_flip_char(tty, 0, TTY_FRAME); + tty_flip_buffer_push(tty); + tty_kref_put(tty); + } + gsm_control_reply(gsm, CMD_RLS, data, clen); +} + +static void gsm_dlci_begin_close(struct gsm_dlci *dlci); + +/** + * gsm_control_message - DLCI 0 control processing + * @gsm: our GSM mux + * @command: the command EA + * @data: data beyond the command/length EAs + * @clen: length + * + * Input processor for control messages from the other end of the link. + * Processes the incoming request and queues a response frame or an + * NSC response if not supported + */ + +static void gsm_control_message(struct gsm_mux *gsm, unsigned int command, + u8 *data, int clen) +{ + u8 buf[1]; + switch (command) { + case CMD_CLD: { + struct gsm_dlci *dlci = gsm->dlci[0]; + /* Modem wishes to close down */ + if (dlci) { + dlci->dead = 1; + gsm->dead = 1; + gsm_dlci_begin_close(dlci); + } + } + break; + case CMD_TEST: + /* Modem wishes to test, reply with the data */ + gsm_control_reply(gsm, CMD_TEST, data, clen); + break; + case CMD_FCON: + /* Modem wants us to STFU */ + gsm->constipated = 1; + gsm_control_reply(gsm, CMD_FCON, NULL, 0); + break; + case CMD_FCOFF: + /* Modem can accept data again */ + gsm->constipated = 0; + gsm_control_reply(gsm, CMD_FCOFF, NULL, 0); + /* Kick the link in case it is idling */ + gsm_data_kick(gsm); + break; + case CMD_MSC: + /* Out of band modem line change indicator for a DLCI */ + gsm_control_modem(gsm, data, clen); + break; + case CMD_RLS: + /* Out of band error reception for a DLCI */ + gsm_control_rls(gsm, data, clen); + break; + case CMD_PSC: + /* Modem wishes to enter power saving state */ + gsm_control_reply(gsm, CMD_PSC, NULL, 0); + break; + /* Optional unsupported commands */ + case CMD_PN: /* Parameter negotiation */ + case CMD_RPN: /* Remote port negotation */ + case CMD_SNC: /* Service negotation command */ + default: + /* Reply to bad commands with an NSC */ + buf[0] = command; + gsm_control_reply(gsm, CMD_NSC, buf, 1); + break; + } +} + +/** + * gsm_control_response - process a response to our control + * @gsm: our GSM mux + * @command: the command (response) EA + * @data: data beyond the command/length EA + * @clen: length + * + * Process a response to an outstanding command. We only allow a single + * control message in flight so this is fairly easy. All the clean up + * is done by the caller, we just update the fields, flag it as done + * and return + */ + +static void gsm_control_response(struct gsm_mux *gsm, unsigned int command, + u8 *data, int clen) +{ + struct gsm_control *ctrl; + unsigned long flags; + + spin_lock_irqsave(&gsm->control_lock, flags); + + ctrl = gsm->pending_cmd; + /* Does the reply match our command */ + command |= 1; + if (ctrl != NULL && (command == ctrl->cmd || command == CMD_NSC)) { + /* Our command was replied to, kill the retry timer */ + del_timer(&gsm->t2_timer); + gsm->pending_cmd = NULL; + /* Rejected by the other end */ + if (command == CMD_NSC) + ctrl->error = -EOPNOTSUPP; + ctrl->done = 1; + wake_up(&gsm->event); + } + spin_unlock_irqrestore(&gsm->control_lock, flags); +} + +/** + * gsm_control_transmit - send control packet + * @gsm: gsm mux + * @ctrl: frame to send + * + * Send out a pending control command (called under control lock) + */ + +static void gsm_control_transmit(struct gsm_mux *gsm, struct gsm_control *ctrl) +{ + struct gsm_msg *msg = gsm_data_alloc(gsm, 0, ctrl->len + 1, + gsm->ftype|PF); + if (msg == NULL) + return; + msg->data[0] = (ctrl->cmd << 1) | 2 | EA; /* command */ + memcpy(msg->data + 1, ctrl->data, ctrl->len); + gsm_data_queue(gsm->dlci[0], msg); +} + +/** + * gsm_control_retransmit - retransmit a control frame + * @data: pointer to our gsm object + * + * Called off the T2 timer expiry in order to retransmit control frames + * that have been lost in the system somewhere. The control_lock protects + * us from colliding with another sender or a receive completion event. + * In that situation the timer may still occur in a small window but + * gsm->pending_cmd will be NULL and we just let the timer expire. + */ + +static void gsm_control_retransmit(unsigned long data) +{ + struct gsm_mux *gsm = (struct gsm_mux *)data; + struct gsm_control *ctrl; + unsigned long flags; + spin_lock_irqsave(&gsm->control_lock, flags); + ctrl = gsm->pending_cmd; + if (ctrl) { + gsm->cretries--; + if (gsm->cretries == 0) { + gsm->pending_cmd = NULL; + ctrl->error = -ETIMEDOUT; + ctrl->done = 1; + spin_unlock_irqrestore(&gsm->control_lock, flags); + wake_up(&gsm->event); + return; + } + gsm_control_transmit(gsm, ctrl); + mod_timer(&gsm->t2_timer, jiffies + gsm->t2 * HZ / 100); + } + spin_unlock_irqrestore(&gsm->control_lock, flags); +} + +/** + * gsm_control_send - send a control frame on DLCI 0 + * @gsm: the GSM channel + * @command: command to send including CR bit + * @data: bytes of data (must be kmalloced) + * @len: length of the block to send + * + * Queue and dispatch a control command. Only one command can be + * active at a time. In theory more can be outstanding but the matching + * gets really complicated so for now stick to one outstanding. + */ + +static struct gsm_control *gsm_control_send(struct gsm_mux *gsm, + unsigned int command, u8 *data, int clen) +{ + struct gsm_control *ctrl = kzalloc(sizeof(struct gsm_control), + GFP_KERNEL); + unsigned long flags; + if (ctrl == NULL) + return NULL; +retry: + wait_event(gsm->event, gsm->pending_cmd == NULL); + spin_lock_irqsave(&gsm->control_lock, flags); + if (gsm->pending_cmd != NULL) { + spin_unlock_irqrestore(&gsm->control_lock, flags); + goto retry; + } + ctrl->cmd = command; + ctrl->data = data; + ctrl->len = clen; + gsm->pending_cmd = ctrl; + gsm->cretries = gsm->n2; + mod_timer(&gsm->t2_timer, jiffies + gsm->t2 * HZ / 100); + gsm_control_transmit(gsm, ctrl); + spin_unlock_irqrestore(&gsm->control_lock, flags); + return ctrl; +} + +/** + * gsm_control_wait - wait for a control to finish + * @gsm: GSM mux + * @control: control we are waiting on + * + * Waits for the control to complete or time out. Frees any used + * resources and returns 0 for success, or an error if the remote + * rejected or ignored the request. + */ + +static int gsm_control_wait(struct gsm_mux *gsm, struct gsm_control *control) +{ + int err; + wait_event(gsm->event, control->done == 1); + err = control->error; + kfree(control); + return err; +} + + +/* + * DLCI level handling: Needs krefs + */ + +/* + * State transitions and timers + */ + +/** + * gsm_dlci_close - a DLCI has closed + * @dlci: DLCI that closed + * + * Perform processing when moving a DLCI into closed state. If there + * is an attached tty this is hung up + */ + +static void gsm_dlci_close(struct gsm_dlci *dlci) +{ + del_timer(&dlci->t1); + if (debug & 8) + printk("DLCI %d goes closed.\n", dlci->addr); + dlci->state = DLCI_CLOSED; + if (dlci->addr != 0) { + struct tty_struct *tty = tty_port_tty_get(&dlci->port); + if (tty) { + tty_hangup(tty); + tty_kref_put(tty); + } + kfifo_reset(dlci->fifo); + } else + dlci->gsm->dead = 1; + wake_up(&dlci->gsm->event); + /* A DLCI 0 close is a MUX termination so we need to kick that + back to userspace somehow */ +} + +/** + * gsm_dlci_open - a DLCI has opened + * @dlci: DLCI that opened + * + * Perform processing when moving a DLCI into open state. + */ + +static void gsm_dlci_open(struct gsm_dlci *dlci) +{ + /* Note that SABM UA .. SABM UA first UA lost can mean that we go + open -> open */ + del_timer(&dlci->t1); + /* This will let a tty open continue */ + dlci->state = DLCI_OPEN; + if (debug & 8) + printk("DLCI %d goes open.\n", dlci->addr); + wake_up(&dlci->gsm->event); +} + +/** + * gsm_dlci_t1 - T1 timer expiry + * @dlci: DLCI that opened + * + * The T1 timer handles retransmits of control frames (essentially of + * SABM and DISC). We resend the command until the retry count runs out + * in which case an opening port goes back to closed and a closing port + * is simply put into closed state (any further frames from the other + * end will get a DM response) + */ + +static void gsm_dlci_t1(unsigned long data) +{ + struct gsm_dlci *dlci = (struct gsm_dlci *)data; + struct gsm_mux *gsm = dlci->gsm; + + switch (dlci->state) { + case DLCI_OPENING: + dlci->retries--; + if (dlci->retries) { + gsm_command(dlci->gsm, dlci->addr, SABM|PF); + mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); + } else + gsm_dlci_close(dlci); + break; + case DLCI_CLOSING: + dlci->retries--; + if (dlci->retries) { + gsm_command(dlci->gsm, dlci->addr, DISC|PF); + mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); + } else + gsm_dlci_close(dlci); + break; + } +} + +/** + * gsm_dlci_begin_open - start channel open procedure + * @dlci: DLCI to open + * + * Commence opening a DLCI from the Linux side. We issue SABM messages + * to the modem which should then reply with a UA, at which point we + * will move into open state. Opening is done asynchronously with retry + * running off timers and the responses. + */ + +static void gsm_dlci_begin_open(struct gsm_dlci *dlci) +{ + struct gsm_mux *gsm = dlci->gsm; + if (dlci->state == DLCI_OPEN || dlci->state == DLCI_OPENING) + return; + dlci->retries = gsm->n2; + dlci->state = DLCI_OPENING; + gsm_command(dlci->gsm, dlci->addr, SABM|PF); + mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); +} + +/** + * gsm_dlci_begin_close - start channel open procedure + * @dlci: DLCI to open + * + * Commence closing a DLCI from the Linux side. We issue DISC messages + * to the modem which should then reply with a UA, at which point we + * will move into closed state. Closing is done asynchronously with retry + * off timers. We may also receive a DM reply from the other end which + * indicates the channel was already closed. + */ + +static void gsm_dlci_begin_close(struct gsm_dlci *dlci) +{ + struct gsm_mux *gsm = dlci->gsm; + if (dlci->state == DLCI_CLOSED || dlci->state == DLCI_CLOSING) + return; + dlci->retries = gsm->n2; + dlci->state = DLCI_CLOSING; + gsm_command(dlci->gsm, dlci->addr, DISC|PF); + mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); +} + +/** + * gsm_dlci_data - data arrived + * @dlci: channel + * @data: block of bytes received + * @len: length of received block + * + * A UI or UIH frame has arrived which contains data for a channel + * other than the control channel. If the relevant virtual tty is + * open we shovel the bits down it, if not we drop them. + */ + +static void gsm_dlci_data(struct gsm_dlci *dlci, u8 *data, int len) +{ + /* krefs .. */ + struct tty_port *port = &dlci->port; + struct tty_struct *tty = tty_port_tty_get(port); + unsigned int modem = 0; + + if (debug & 16) + printk("%d bytes for tty %p\n", len, tty); + if (tty) { + switch (dlci->adaption) { + /* Unsupported types */ + /* Packetised interruptible data */ + case 4: + break; + /* Packetised uininterruptible voice/data */ + case 3: + break; + /* Asynchronous serial with line state in each frame */ + case 2: + while (gsm_read_ea(&modem, *data++) == 0) { + len--; + if (len == 0) + return; + } + gsm_process_modem(tty, dlci, modem); + /* Line state will go via DLCI 0 controls only */ + case 1: + default: + tty_insert_flip_string(tty, data, len); + tty_flip_buffer_push(tty); + } + tty_kref_put(tty); + } +} + +/** + * gsm_dlci_control - data arrived on control channel + * @dlci: channel + * @data: block of bytes received + * @len: length of received block + * + * A UI or UIH frame has arrived which contains data for DLCI 0 the + * control channel. This should contain a command EA followed by + * control data bytes. The command EA contains a command/response bit + * and we divide up the work accordingly. + */ + +static void gsm_dlci_command(struct gsm_dlci *dlci, u8 *data, int len) +{ + /* See what command is involved */ + unsigned int command = 0; + while (len-- > 0) { + if (gsm_read_ea(&command, *data++) == 1) { + int clen = *data++; + len--; + /* FIXME: this is properly an EA */ + clen >>= 1; + /* Malformed command ? */ + if (clen > len) + return; + if (command & 1) + gsm_control_message(dlci->gsm, command, + data, clen); + else + gsm_control_response(dlci->gsm, command, + data, clen); + return; + } + } +} + +/* + * Allocate/Free DLCI channels + */ + +/** + * gsm_dlci_alloc - allocate a DLCI + * @gsm: GSM mux + * @addr: address of the DLCI + * + * Allocate and install a new DLCI object into the GSM mux. + * + * FIXME: review locking races + */ + +static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr) +{ + struct gsm_dlci *dlci = kzalloc(sizeof(struct gsm_dlci), GFP_ATOMIC); + if (dlci == NULL) + return NULL; + spin_lock_init(&dlci->lock); + dlci->fifo = &dlci->_fifo; + if (kfifo_alloc(&dlci->_fifo, 4096, GFP_KERNEL) < 0) { + kfree(dlci); + return NULL; + } + + skb_queue_head_init(&dlci->skb_list); + init_timer(&dlci->t1); + dlci->t1.function = gsm_dlci_t1; + dlci->t1.data = (unsigned long)dlci; + tty_port_init(&dlci->port); + dlci->port.ops = &gsm_port_ops; + dlci->gsm = gsm; + dlci->addr = addr; + dlci->adaption = gsm->adaption; + dlci->state = DLCI_CLOSED; + if (addr) + dlci->data = gsm_dlci_data; + else + dlci->data = gsm_dlci_command; + gsm->dlci[addr] = dlci; + return dlci; +} + +/** + * gsm_dlci_free - release DLCI + * @dlci: DLCI to destroy + * + * Free up a DLCI. Currently to keep the lifetime rules sane we only + * clean up DLCI objects when the MUX closes rather than as the port + * is closed down on both the tty and mux levels. + * + * Can sleep. + */ +static void gsm_dlci_free(struct gsm_dlci *dlci) +{ + struct tty_struct *tty = tty_port_tty_get(&dlci->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + del_timer_sync(&dlci->t1); + dlci->gsm->dlci[dlci->addr] = NULL; + kfifo_free(dlci->fifo); + kfree(dlci); +} + + +/* + * LAPBish link layer logic + */ + +/** + * gsm_queue - a GSM frame is ready to process + * @gsm: pointer to our gsm mux + * + * At this point in time a frame has arrived and been demangled from + * the line encoding. All the differences between the encodings have + * been handled below us and the frame is unpacked into the structures. + * The fcs holds the header FCS but any data FCS must be added here. + */ + +static void gsm_queue(struct gsm_mux *gsm) +{ + struct gsm_dlci *dlci; + u8 cr; + int address; + /* We have to sneak a look at the packet body to do the FCS. + A somewhat layering violation in the spec */ + + if ((gsm->control & ~PF) == UI) + gsm->fcs = gsm_fcs_add_block(gsm->fcs, gsm->buf, gsm->len); + if (gsm->fcs != GOOD_FCS) { + gsm->bad_fcs++; + if (debug & 4) + printk("BAD FCS %02x\n", gsm->fcs); + return; + } + address = gsm->address >> 1; + if (address >= NUM_DLCI) + goto invalid; + + cr = gsm->address & 1; /* C/R bit */ + + gsm_print_packet("<--", address, cr, gsm->control, gsm->buf, gsm->len); + + cr ^= 1 - gsm->initiator; /* Flip so 1 always means command */ + dlci = gsm->dlci[address]; + + switch (gsm->control) { + case SABM|PF: + if (cr == 0) + goto invalid; + if (dlci == NULL) + dlci = gsm_dlci_alloc(gsm, address); + if (dlci == NULL) + return; + if (dlci->dead) + gsm_response(gsm, address, DM); + else { + gsm_response(gsm, address, UA); + gsm_dlci_open(dlci); + } + break; + case DISC|PF: + if (cr == 0) + goto invalid; + if (dlci == NULL || dlci->state == DLCI_CLOSED) { + gsm_response(gsm, address, DM); + return; + } + /* Real close complete */ + gsm_response(gsm, address, UA); + gsm_dlci_close(dlci); + break; + case UA: + case UA|PF: + if (cr == 0 || dlci == NULL) + break; + switch (dlci->state) { + case DLCI_CLOSING: + gsm_dlci_close(dlci); + break; + case DLCI_OPENING: + gsm_dlci_open(dlci); + break; + } + break; + case DM: /* DM can be valid unsolicited */ + case DM|PF: + if (cr) + goto invalid; + if (dlci == NULL) + return; + gsm_dlci_close(dlci); + break; + case UI: + case UI|PF: + case UIH: + case UIH|PF: +#if 0 + if (cr) + goto invalid; +#endif + if (dlci == NULL || dlci->state != DLCI_OPEN) { + gsm_command(gsm, address, DM|PF); + return; + } + dlci->data(dlci, gsm->buf, gsm->len); + break; + default: + goto invalid; + } + return; +invalid: + gsm->malformed++; + return; +} + + +/** + * gsm0_receive - perform processing for non-transparency + * @gsm: gsm data for this ldisc instance + * @c: character + * + * Receive bytes in gsm mode 0 + */ + +static void gsm0_receive(struct gsm_mux *gsm, unsigned char c) +{ + switch (gsm->state) { + case GSM_SEARCH: /* SOF marker */ + if (c == GSM0_SOF) { + gsm->state = GSM_ADDRESS; + gsm->address = 0; + gsm->len = 0; + gsm->fcs = INIT_FCS; + } + break; /* Address EA */ + case GSM_ADDRESS: + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + if (gsm_read_ea(&gsm->address, c)) + gsm->state = GSM_CONTROL; + break; + case GSM_CONTROL: /* Control Byte */ + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + gsm->control = c; + gsm->state = GSM_LEN; + break; + case GSM_LEN: /* Length EA */ + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + if (gsm_read_ea(&gsm->len, c)) { + if (gsm->len > gsm->mru) { + gsm->bad_size++; + gsm->state = GSM_SEARCH; + break; + } + gsm->count = 0; + gsm->state = GSM_DATA; + } + break; + case GSM_DATA: /* Data */ + gsm->buf[gsm->count++] = c; + if (gsm->count == gsm->len) + gsm->state = GSM_FCS; + break; + case GSM_FCS: /* FCS follows the packet */ + gsm->fcs = c; + gsm_queue(gsm); + /* And then back for the next frame */ + gsm->state = GSM_SEARCH; + break; + } +} + +/** + * gsm0_receive - perform processing for non-transparency + * @gsm: gsm data for this ldisc instance + * @c: character + * + * Receive bytes in mode 1 (Advanced option) + */ + +static void gsm1_receive(struct gsm_mux *gsm, unsigned char c) +{ + if (c == GSM1_SOF) { + /* EOF is only valid in frame if we have got to the data state + and received at least one byte (the FCS) */ + if (gsm->state == GSM_DATA && gsm->count) { + /* Extract the FCS */ + gsm->count--; + gsm->fcs = gsm_fcs_add(gsm->fcs, gsm->buf[gsm->count]); + gsm->len = gsm->count; + gsm_queue(gsm); + gsm->state = GSM_START; + return; + } + /* Any partial frame was a runt so go back to start */ + if (gsm->state != GSM_START) { + gsm->malformed++; + gsm->state = GSM_START; + } + /* A SOF in GSM_START means we are still reading idling or + framing bytes */ + return; + } + + if (c == GSM1_ESCAPE) { + gsm->escape = 1; + return; + } + + /* Only an unescaped SOF gets us out of GSM search */ + if (gsm->state == GSM_SEARCH) + return; + + if (gsm->escape) { + c ^= GSM1_ESCAPE_BITS; + gsm->escape = 0; + } + switch (gsm->state) { + case GSM_START: /* First byte after SOF */ + gsm->address = 0; + gsm->state = GSM_ADDRESS; + gsm->fcs = INIT_FCS; + /* Drop through */ + case GSM_ADDRESS: /* Address continuation */ + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + if (gsm_read_ea(&gsm->address, c)) + gsm->state = GSM_CONTROL; + break; + case GSM_CONTROL: /* Control Byte */ + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + gsm->control = c; + gsm->count = 0; + gsm->state = GSM_DATA; + break; + case GSM_DATA: /* Data */ + if (gsm->count > gsm->mru ) { /* Allow one for the FCS */ + gsm->state = GSM_OVERRUN; + gsm->bad_size++; + } else + gsm->buf[gsm->count++] = c; + break; + case GSM_OVERRUN: /* Over-long - eg a dropped SOF */ + break; + } +} + +/** + * gsm_error - handle tty error + * @gsm: ldisc data + * @data: byte received (may be invalid) + * @flag: error received + * + * Handle an error in the receipt of data for a frame. Currently we just + * go back to hunting for a SOF. + * + * FIXME: better diagnostics ? + */ + +static void gsm_error(struct gsm_mux *gsm, + unsigned char data, unsigned char flag) +{ + gsm->state = GSM_SEARCH; + gsm->io_error++; +} + +/** + * gsm_cleanup_mux - generic GSM protocol cleanup + * @gsm: our mux + * + * Clean up the bits of the mux which are the same for all framing + * protocols. Remove the mux from the mux table, stop all the timers + * and then shut down each device hanging up the channels as we go. + */ + +void gsm_cleanup_mux(struct gsm_mux *gsm) +{ + int i; + struct gsm_dlci *dlci = gsm->dlci[0]; + struct gsm_msg *txq; + + gsm->dead = 1; + + spin_lock(&gsm_mux_lock); + for (i = 0; i < MAX_MUX; i++) { + if (gsm_mux[i] == gsm) { + gsm_mux[i] = NULL; + break; + } + } + spin_unlock(&gsm_mux_lock); + WARN_ON(i == MAX_MUX); + + del_timer_sync(&gsm->t2_timer); + /* Now we are sure T2 has stopped */ + if (dlci) { + dlci->dead = 1; + gsm_dlci_begin_close(dlci); + wait_event_interruptible(gsm->event, + dlci->state == DLCI_CLOSED); + } + /* Free up any link layer users */ + for (i = 0; i < NUM_DLCI; i++) + if (gsm->dlci[i]) + gsm_dlci_free(gsm->dlci[i]); + /* Now wipe the queues */ + for (txq = gsm->tx_head; txq != NULL; txq = gsm->tx_head) { + gsm->tx_head = txq->next; + kfree(txq); + } + gsm->tx_tail = NULL; +} +EXPORT_SYMBOL_GPL(gsm_cleanup_mux); + +/** + * gsm_activate_mux - generic GSM setup + * @gsm: our mux + * + * Set up the bits of the mux which are the same for all framing + * protocols. Add the mux to the mux table so it can be opened and + * finally kick off connecting to DLCI 0 on the modem. + */ + +int gsm_activate_mux(struct gsm_mux *gsm) +{ + struct gsm_dlci *dlci; + int i = 0; + + init_timer(&gsm->t2_timer); + gsm->t2_timer.function = gsm_control_retransmit; + gsm->t2_timer.data = (unsigned long)gsm; + init_waitqueue_head(&gsm->event); + spin_lock_init(&gsm->control_lock); + spin_lock_init(&gsm->tx_lock); + + if (gsm->encoding == 0) + gsm->receive = gsm0_receive; + else + gsm->receive = gsm1_receive; + gsm->error = gsm_error; + + spin_lock(&gsm_mux_lock); + for (i = 0; i < MAX_MUX; i++) { + if (gsm_mux[i] == NULL) { + gsm_mux[i] = gsm; + break; + } + } + spin_unlock(&gsm_mux_lock); + if (i == MAX_MUX) + return -EBUSY; + + dlci = gsm_dlci_alloc(gsm, 0); + if (dlci == NULL) + return -ENOMEM; + gsm->dead = 0; /* Tty opens are now permissible */ + return 0; +} +EXPORT_SYMBOL_GPL(gsm_activate_mux); + +/** + * gsm_free_mux - free up a mux + * @mux: mux to free + * + * Dispose of allocated resources for a dead mux. No refcounting + * at present so the mux must be truely dead. + */ +void gsm_free_mux(struct gsm_mux *gsm) +{ + kfree(gsm->txframe); + kfree(gsm->buf); + kfree(gsm); +} +EXPORT_SYMBOL_GPL(gsm_free_mux); + +/** + * gsm_alloc_mux - allocate a mux + * + * Creates a new mux ready for activation. + */ + +struct gsm_mux *gsm_alloc_mux(void) +{ + struct gsm_mux *gsm = kzalloc(sizeof(struct gsm_mux), GFP_KERNEL); + if (gsm == NULL) + return NULL; + gsm->buf = kmalloc(MAX_MRU + 1, GFP_KERNEL); + if (gsm->buf == NULL) { + kfree(gsm); + return NULL; + } + gsm->txframe = kmalloc(2 * MAX_MRU + 2, GFP_KERNEL); + if (gsm->txframe == NULL) { + kfree(gsm->buf); + kfree(gsm); + return NULL; + } + spin_lock_init(&gsm->lock); + + gsm->t1 = T1; + gsm->t2 = T2; + gsm->n2 = N2; + gsm->ftype = UIH; + gsm->initiator = 0; + gsm->adaption = 1; + gsm->encoding = 1; + gsm->mru = 64; /* Default to encoding 1 so these should be 64 */ + gsm->mtu = 64; + gsm->dead = 1; /* Avoid early tty opens */ + + return gsm; +} +EXPORT_SYMBOL_GPL(gsm_alloc_mux); + + + + +/** + * gsmld_output - write to link + * @gsm: our mux + * @data: bytes to output + * @len: size + * + * Write a block of data from the GSM mux to the data channel. This + * will eventually be serialized from above but at the moment isn't. + */ + +static int gsmld_output(struct gsm_mux *gsm, u8 *data, int len) +{ + if (tty_write_room(gsm->tty) < len) { + set_bit(TTY_DO_WRITE_WAKEUP, &gsm->tty->flags); + return -ENOSPC; + } + if (debug & 4) { + printk("-->%d bytes out\n", len); + hex_packet(data, len); + } + gsm->tty->ops->write(gsm->tty, data, len); + return len; +} + +/** + * gsmld_attach_gsm - mode set up + * @tty: our tty structure + * @gsm: our mux + * + * Set up the MUX for basic mode and commence connecting to the + * modem. Currently called from the line discipline set up but + * will need moving to an ioctl path. + */ + +static int gsmld_attach_gsm(struct tty_struct *tty, struct gsm_mux *gsm) +{ + int ret; + + gsm->tty = tty_kref_get(tty); + gsm->output = gsmld_output; + ret = gsm_activate_mux(gsm); + if (ret != 0) + tty_kref_put(gsm->tty); + return ret; +} + + +/** + * gsmld_detach_gsm - stop doing 0710 mux + * @tty: tty atttached to the mux + * @gsm: mux + * + * Shutdown and then clean up the resources used by the line discipline + */ + +static void gsmld_detach_gsm(struct tty_struct *tty, struct gsm_mux *gsm) +{ + WARN_ON(tty != gsm->tty); + gsm_cleanup_mux(gsm); + tty_kref_put(gsm->tty); + gsm->tty = NULL; +} + +static void gsmld_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct gsm_mux *gsm = tty->disc_data; + const unsigned char *dp; + char *f; + int i; + char buf[64]; + char flags; + + if (debug & 4) { + printk("Inbytes %dd\n", count); + hex_packet(cp, count); + } + + for (i = count, dp = cp, f = fp; i; i--, dp++) { + flags = *f++; + switch (flags) { + case TTY_NORMAL: + gsm->receive(gsm, *dp); + break; + case TTY_OVERRUN: + case TTY_BREAK: + case TTY_PARITY: + case TTY_FRAME: + gsm->error(gsm, *dp, flags); + break; + default: + printk(KERN_ERR "%s: unknown flag %d\n", + tty_name(tty, buf), flags); + break; + } + } + /* FASYNC if needed ? */ + /* If clogged call tty_throttle(tty); */ +} + +/** + * gsmld_chars_in_buffer - report available bytes + * @tty: tty device + * + * Report the number of characters buffered to be delivered to user + * at this instant in time. + * + * Locking: gsm lock + */ + +static ssize_t gsmld_chars_in_buffer(struct tty_struct *tty) +{ + return 0; +} + +/** + * gsmld_flush_buffer - clean input queue + * @tty: terminal device + * + * Flush the input buffer. Called when the line discipline is + * being closed, when the tty layer wants the buffer flushed (eg + * at hangup). + */ + +static void gsmld_flush_buffer(struct tty_struct *tty) +{ +} + +/** + * gsmld_close - close the ldisc for this tty + * @tty: device + * + * Called from the terminal layer when this line discipline is + * being shut down, either because of a close or becsuse of a + * discipline change. The function will not be called while other + * ldisc methods are in progress. + */ + +static void gsmld_close(struct tty_struct *tty) +{ + struct gsm_mux *gsm = tty->disc_data; + + gsmld_detach_gsm(tty, gsm); + + gsmld_flush_buffer(tty); + /* Do other clean up here */ + gsm_free_mux(gsm); +} + +/** + * gsmld_open - open an ldisc + * @tty: terminal to open + * + * Called when this line discipline is being attached to the + * terminal device. Can sleep. Called serialized so that no + * other events will occur in parallel. No further open will occur + * until a close. + */ + +static int gsmld_open(struct tty_struct *tty) +{ + struct gsm_mux *gsm; + + if (tty->ops->write == NULL) + return -EINVAL; + + /* Attach our ldisc data */ + gsm = gsm_alloc_mux(); + if (gsm == NULL) + return -ENOMEM; + + tty->disc_data = gsm; + tty->receive_room = 65536; + + /* Attach the initial passive connection */ + gsm->encoding = 1; + return gsmld_attach_gsm(tty, gsm); +} + +/** + * gsmld_write_wakeup - asynchronous I/O notifier + * @tty: tty device + * + * Required for the ptys, serial driver etc. since processes + * that attach themselves to the master and rely on ASYNC + * IO must be woken up + */ + +static void gsmld_write_wakeup(struct tty_struct *tty) +{ + struct gsm_mux *gsm = tty->disc_data; + unsigned long flags; + + /* Queue poll */ + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + gsm_data_kick(gsm); + if (gsm->tx_bytes < TX_THRESH_LO) { + spin_lock_irqsave(&gsm->tx_lock, flags); + gsm_dlci_data_sweep(gsm); + spin_unlock_irqrestore(&gsm->tx_lock, flags); + } +} + +/** + * gsmld_read - read function for tty + * @tty: tty device + * @file: file object + * @buf: userspace buffer pointer + * @nr: size of I/O + * + * Perform reads for the line discipline. We are guaranteed that the + * line discipline will not be closed under us but we may get multiple + * parallel readers and must handle this ourselves. We may also get + * a hangup. Always called in user context, may sleep. + * + * This code must be sure never to sleep through a hangup. + */ + +static ssize_t gsmld_read(struct tty_struct *tty, struct file *file, + unsigned char __user *buf, size_t nr) +{ + return -EOPNOTSUPP; +} + +/** + * gsmld_write - write function for tty + * @tty: tty device + * @file: file object + * @buf: userspace buffer pointer + * @nr: size of I/O + * + * Called when the owner of the device wants to send a frame + * itself (or some other control data). The data is transferred + * as-is and must be properly framed and checksummed as appropriate + * by userspace. Frames are either sent whole or not at all as this + * avoids pain user side. + */ + +static ssize_t gsmld_write(struct tty_struct *tty, struct file *file, + const unsigned char *buf, size_t nr) +{ + int space = tty_write_room(tty); + if (space >= nr) + return tty->ops->write(tty, buf, nr); + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + return -ENOBUFS; +} + +/** + * gsmld_poll - poll method for N_GSM0710 + * @tty: terminal device + * @file: file accessing it + * @wait: poll table + * + * Called when the line discipline is asked to poll() for data or + * for special events. This code is not serialized with respect to + * other events save open/close. + * + * This code must be sure never to sleep through a hangup. + * Called without the kernel lock held - fine + */ + +static unsigned int gsmld_poll(struct tty_struct *tty, struct file *file, + poll_table *wait) +{ + unsigned int mask = 0; + struct gsm_mux *gsm = tty->disc_data; + + poll_wait(file, &tty->read_wait, wait); + poll_wait(file, &tty->write_wait, wait); + if (tty_hung_up_p(file)) + mask |= POLLHUP; + if (!tty_is_writelocked(tty) && tty_write_room(tty) > 0) + mask |= POLLOUT | POLLWRNORM; + if (gsm->dead) + mask |= POLLHUP; + return mask; +} + +static int gsmld_config(struct tty_struct *tty, struct gsm_mux *gsm, + struct gsm_config *c) +{ + int need_close = 0; + int need_restart = 0; + + /* Stuff we don't support yet - UI or I frame transport, windowing */ + if ((c->adaption !=1 && c->adaption != 2) || c->k) + return -EOPNOTSUPP; + /* Check the MRU/MTU range looks sane */ + if (c->mru > MAX_MRU || c->mtu > MAX_MTU || c->mru < 8 || c->mtu < 8) + return -EINVAL; + if (c->n2 < 3) + return -EINVAL; + if (c->encapsulation > 1) /* Basic, advanced, no I */ + return -EINVAL; + if (c->initiator > 1) + return -EINVAL; + if (c->i == 0 || c->i > 2) /* UIH and UI only */ + return -EINVAL; + /* + * See what is needed for reconfiguration + */ + + /* Timing fields */ + if (c->t1 != 0 && c->t1 != gsm->t1) + need_restart = 1; + if (c->t2 != 0 && c->t2 != gsm->t2) + need_restart = 1; + if (c->encapsulation != gsm->encoding) + need_restart = 1; + if (c->adaption != gsm->adaption) + need_restart = 1; + /* Requires care */ + if (c->initiator != gsm->initiator) + need_close = 1; + if (c->mru != gsm->mru) + need_restart = 1; + if (c->mtu != gsm->mtu) + need_restart = 1; + + /* + * Close down what is needed, restart and initiate the new + * configuration + */ + + if (need_close || need_restart) { + gsm_dlci_begin_close(gsm->dlci[0]); + /* This will timeout if the link is down due to N2 expiring */ + wait_event_interruptible(gsm->event, + gsm->dlci[0]->state == DLCI_CLOSED); + if (signal_pending(current)) + return -EINTR; + } + if (need_restart) + gsm_cleanup_mux(gsm); + + gsm->initiator = c->initiator; + gsm->mru = c->mru; + gsm->encoding = c->encapsulation; + gsm->adaption = c->adaption; + + if (c->i == 1) + gsm->ftype = UIH; + else if (c->i == 2) + gsm->ftype = UI; + + if (c->t1) + gsm->t1 = c->t1; + if (c->t2) + gsm->t2 = c->t2; + + /* FIXME: We need to separate activation/deactivation from adding + and removing from the mux array */ + if (need_restart) + gsm_activate_mux(gsm); + if (gsm->initiator && need_close) + gsm_dlci_begin_open(gsm->dlci[0]); + return 0; +} + +static int gsmld_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct gsm_config c; + struct gsm_mux *gsm = tty->disc_data; + + switch (cmd) { + case GSMIOC_GETCONF: + memset(&c, 0, sizeof(c)); + c.adaption = gsm->adaption; + c.encapsulation = gsm->encoding; + c.initiator = gsm->initiator; + c.t1 = gsm->t1; + c.t2 = gsm->t2; + c.t3 = 0; /* Not supported */ + c.n2 = gsm->n2; + if (gsm->ftype == UIH) + c.i = 1; + else + c.i = 2; + printk("Ftype %d i %d\n", gsm->ftype, c.i); + c.mru = gsm->mru; + c.mtu = gsm->mtu; + c.k = 0; + if (copy_to_user((void *)arg, &c, sizeof(c))) + return -EFAULT; + return 0; + case GSMIOC_SETCONF: + if (copy_from_user(&c, (void *)arg, sizeof(c))) + return -EFAULT; + return gsmld_config(tty, gsm, &c); + default: + return n_tty_ioctl_helper(tty, file, cmd, arg); + } +} + + +/* Line discipline for real tty */ +struct tty_ldisc_ops tty_ldisc_packet = { + .owner = THIS_MODULE, + .magic = TTY_LDISC_MAGIC, + .name = "n_gsm", + .open = gsmld_open, + .close = gsmld_close, + .flush_buffer = gsmld_flush_buffer, + .chars_in_buffer = gsmld_chars_in_buffer, + .read = gsmld_read, + .write = gsmld_write, + .ioctl = gsmld_ioctl, + .poll = gsmld_poll, + .receive_buf = gsmld_receive_buf, + .write_wakeup = gsmld_write_wakeup +}; + +/* + * Virtual tty side + */ + +#define TX_SIZE 512 + +static int gsmtty_modem_update(struct gsm_dlci *dlci, u8 brk) +{ + u8 modembits[5]; + struct gsm_control *ctrl; + int len = 2; + + if (brk) + len++; + + modembits[0] = len << 1 | EA; /* Data bytes */ + modembits[1] = dlci->addr << 2 | 3; /* DLCI, EA, 1 */ + modembits[2] = gsm_encode_modem(dlci) << 1 | EA; + if (brk) + modembits[3] = brk << 4 | 2 | EA; /* Valid, EA */ + ctrl = gsm_control_send(dlci->gsm, CMD_MSC, modembits, len + 1); + if (ctrl == NULL) + return -ENOMEM; + return gsm_control_wait(dlci->gsm, ctrl); +} + +static int gsm_carrier_raised(struct tty_port *port) +{ + struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port); + /* Not yet open so no carrier info */ + if (dlci->state != DLCI_OPEN) + return 0; + if (debug & 2) + return 1; + return dlci->modem_rx & TIOCM_CD; +} + +static void gsm_dtr_rts(struct tty_port *port, int onoff) +{ + struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port); + unsigned int modem_tx = dlci->modem_tx; + if (onoff) + modem_tx |= TIOCM_DTR | TIOCM_RTS; + else + modem_tx &= ~(TIOCM_DTR | TIOCM_RTS); + if (modem_tx != dlci->modem_tx) { + dlci->modem_tx = modem_tx; + gsmtty_modem_update(dlci, 0); + } +} + +static const struct tty_port_operations gsm_port_ops = { + .carrier_raised = gsm_carrier_raised, + .dtr_rts = gsm_dtr_rts, +}; + + +static int gsmtty_open(struct tty_struct *tty, struct file *filp) +{ + struct gsm_mux *gsm; + struct gsm_dlci *dlci; + struct tty_port *port; + unsigned int line = tty->index; + unsigned int mux = line >> 6; + + line = line & 0x3F; + + if (mux >= MAX_MUX) + return -ENXIO; + /* FIXME: we need to lock gsm_mux for lifetimes of ttys eventually */ + if (gsm_mux[mux] == NULL) + return -EUNATCH; + if (line == 0 || line > 61) /* 62/63 reserved */ + return -ECHRNG; + gsm = gsm_mux[mux]; + if (gsm->dead) + return -EL2HLT; + dlci = gsm->dlci[line]; + if (dlci == NULL) + dlci = gsm_dlci_alloc(gsm, line); + if (dlci == NULL) + return -ENOMEM; + port = &dlci->port; + port->count++; + tty->driver_data = dlci; + tty_port_tty_set(port, tty); + + dlci->modem_rx = 0; + /* We could in theory open and close before we wait - eg if we get + a DM straight back. This is ok as that will have caused a hangup */ + set_bit(ASYNCB_INITIALIZED, &port->flags); + /* Start sending off SABM messages */ + gsm_dlci_begin_open(dlci); + /* And wait for virtual carrier */ + return tty_port_block_til_ready(port, tty, filp); +} + +static void gsmtty_close(struct tty_struct *tty, struct file *filp) +{ + struct gsm_dlci *dlci = tty->driver_data; + if (dlci == NULL) + return; + if (tty_port_close_start(&dlci->port, tty, filp) == 0) + return; + gsm_dlci_begin_close(dlci); + tty_port_close_end(&dlci->port, tty); + tty_port_tty_set(&dlci->port, NULL); +} + +static void gsmtty_hangup(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + tty_port_hangup(&dlci->port); + gsm_dlci_begin_close(dlci); +} + +static int gsmtty_write(struct tty_struct *tty, const unsigned char *buf, + int len) +{ + struct gsm_dlci *dlci = tty->driver_data; + /* Stuff the bytes into the fifo queue */ + int sent = kfifo_in_locked(dlci->fifo, buf, len, &dlci->lock); + /* Need to kick the channel */ + gsm_dlci_data_kick(dlci); + return sent; +} + +static int gsmtty_write_room(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + return TX_SIZE - kfifo_len(dlci->fifo); +} + +static int gsmtty_chars_in_buffer(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + return kfifo_len(dlci->fifo); +} + +static void gsmtty_flush_buffer(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + /* Caution needed: If we implement reliable transport classes + then the data being transmitted can't simply be junked once + it has first hit the stack. Until then we can just blow it + away */ + kfifo_reset(dlci->fifo); + /* Need to unhook this DLCI from the transmit queue logic */ +} + +static void gsmtty_wait_until_sent(struct tty_struct *tty, int timeout) +{ + /* The FIFO handles the queue so the kernel will do the right + thing waiting on chars_in_buffer before calling us. No work + to do here */ +} + +static int gsmtty_tiocmget(struct tty_struct *tty, struct file *filp) +{ + struct gsm_dlci *dlci = tty->driver_data; + return dlci->modem_rx; +} + +static int gsmtty_tiocmset(struct tty_struct *tty, struct file *filp, + unsigned int set, unsigned int clear) +{ + struct gsm_dlci *dlci = tty->driver_data; + unsigned int modem_tx = dlci->modem_tx; + + modem_tx &= clear; + modem_tx |= set; + + if (modem_tx != dlci->modem_tx) { + dlci->modem_tx = modem_tx; + return gsmtty_modem_update(dlci, 0); + } + return 0; +} + + +static int gsmtty_ioctl(struct tty_struct *tty, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static void gsmtty_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + /* For the moment its fixed. In actual fact the speed information + for the virtual channel can be propogated in both directions by + the RPN control message. This however rapidly gets nasty as we + then have to remap modem signals each way according to whether + our virtual cable is null modem etc .. */ + tty_termios_copy_hw(tty->termios, old); +} + +static void gsmtty_throttle(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + if (tty->termios->c_cflag & CRTSCTS) + dlci->modem_tx &= ~TIOCM_DTR; + dlci->throttled = 1; + /* Send an MSC with DTR cleared */ + gsmtty_modem_update(dlci, 0); +} + +static void gsmtty_unthrottle(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + if (tty->termios->c_cflag & CRTSCTS) + dlci->modem_tx |= TIOCM_DTR; + dlci->throttled = 0; + /* Send an MSC with DTR set */ + gsmtty_modem_update(dlci, 0); +} + +static int gsmtty_break_ctl(struct tty_struct *tty, int state) +{ + struct gsm_dlci *dlci = tty->driver_data; + int encode = 0; /* Off */ + + if (state == -1) /* "On indefinitely" - we can't encode this + properly */ + encode = 0x0F; + else if (state > 0) { + encode = state / 200; /* mS to encoding */ + if (encode > 0x0F) + encode = 0x0F; /* Best effort */ + } + return gsmtty_modem_update(dlci, encode); +} + +static struct tty_driver *gsm_tty_driver; + +/* Virtual ttys for the demux */ +static const struct tty_operations gsmtty_ops = { + .open = gsmtty_open, + .close = gsmtty_close, + .write = gsmtty_write, + .write_room = gsmtty_write_room, + .chars_in_buffer = gsmtty_chars_in_buffer, + .flush_buffer = gsmtty_flush_buffer, + .ioctl = gsmtty_ioctl, + .throttle = gsmtty_throttle, + .unthrottle = gsmtty_unthrottle, + .set_termios = gsmtty_set_termios, + .hangup = gsmtty_hangup, + .wait_until_sent = gsmtty_wait_until_sent, + .tiocmget = gsmtty_tiocmget, + .tiocmset = gsmtty_tiocmset, + .break_ctl = gsmtty_break_ctl, +}; + + + +static int __init gsm_init(void) +{ + /* Fill in our line protocol discipline, and register it */ + int status = tty_register_ldisc(N_GSM0710, &tty_ldisc_packet); + if (status != 0) { + printk(KERN_ERR "n_gsm: can't register line discipline (err = %d)\n", status); + return status; + } + + gsm_tty_driver = alloc_tty_driver(256); + if (!gsm_tty_driver) { + tty_unregister_ldisc(N_GSM0710); + printk(KERN_ERR "gsm_init: tty allocation failed.\n"); + return -EINVAL; + } + gsm_tty_driver->owner = THIS_MODULE; + gsm_tty_driver->driver_name = "gsmtty"; + gsm_tty_driver->name = "gsmtty"; + gsm_tty_driver->major = 0; /* Dynamic */ + gsm_tty_driver->minor_start = 0; + gsm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + gsm_tty_driver->subtype = SERIAL_TYPE_NORMAL; + gsm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV + | TTY_DRIVER_HARDWARE_BREAK; + gsm_tty_driver->init_termios = tty_std_termios; + /* Fixme */ + gsm_tty_driver->init_termios.c_lflag &= ~ECHO; + tty_set_operations(gsm_tty_driver, &gsmtty_ops); + + spin_lock_init(&gsm_mux_lock); + + if (tty_register_driver(gsm_tty_driver)) { + put_tty_driver(gsm_tty_driver); + tty_unregister_ldisc(N_GSM0710); + printk(KERN_ERR "gsm_init: tty registration failed.\n"); + return -EBUSY; + } + printk(KERN_INFO "gsm_init: loaded as %d,%d.\n", gsm_tty_driver->major, gsm_tty_driver->minor_start); + return 0; +} + +static void __exit gsm_exit(void) +{ + int status = tty_unregister_ldisc(N_GSM0710); + if (status != 0) + printk(KERN_ERR "n_gsm: can't unregister line discipline (err = %d)\n", status); + tty_unregister_driver(gsm_tty_driver); + put_tty_driver(gsm_tty_driver); + printk(KERN_INFO "gsm_init: unloaded.\n"); +} + +module_init(gsm_init); +module_exit(gsm_exit); + + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_GSM0710); diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c new file mode 100644 index 000000000000..47d32281032c --- /dev/null +++ b/drivers/tty/n_hdlc.c @@ -0,0 +1,1007 @@ +/* generic HDLC line discipline for Linux + * + * Written by Paul Fulghum paulkf@microgate.com + * for Microgate Corporation + * + * Microgate and SyncLink are registered trademarks of Microgate Corporation + * + * Adapted from ppp.c, written by Michael Callahan <callahan@maths.ox.ac.uk>, + * Al Longyear <longyear@netcom.com>, + * Paul Mackerras <Paul.Mackerras@cs.anu.edu.au> + * + * Original release 01/11/99 + * + * This code is released under the GNU General Public License (GPL) + * + * This module implements the tty line discipline N_HDLC for use with + * tty device drivers that support bit-synchronous HDLC communications. + * + * All HDLC data is frame oriented which means: + * + * 1. tty write calls represent one complete transmit frame of data + * The device driver should accept the complete frame or none of + * the frame (busy) in the write method. Each write call should have + * a byte count in the range of 2-65535 bytes (2 is min HDLC frame + * with 1 addr byte and 1 ctrl byte). The max byte count of 65535 + * should include any crc bytes required. For example, when using + * CCITT CRC32, 4 crc bytes are required, so the maximum size frame + * the application may transmit is limited to 65531 bytes. For CCITT + * CRC16, the maximum application frame size would be 65533. + * + * + * 2. receive callbacks from the device driver represents + * one received frame. The device driver should bypass + * the tty flip buffer and call the line discipline receive + * callback directly to avoid fragmenting or concatenating + * multiple frames into a single receive callback. + * + * The HDLC line discipline queues the receive frames in separate + * buffers so complete receive frames can be returned by the + * tty read calls. + * + * 3. tty read calls returns an entire frame of data or nothing. + * + * 4. all send and receive data is considered raw. No processing + * or translation is performed by the line discipline, regardless + * of the tty flags + * + * 5. When line discipline is queried for the amount of receive + * data available (FIOC), 0 is returned if no data available, + * otherwise the count of the next available frame is returned. + * (instead of the sum of all received frame counts). + * + * These conventions allow the standard tty programming interface + * to be used for synchronous HDLC applications when used with + * this line discipline (or another line discipline that is frame + * oriented such as N_PPP). + * + * The SyncLink driver (synclink.c) implements both asynchronous + * (using standard line discipline N_TTY) and synchronous HDLC + * (using N_HDLC) communications, with the latter using the above + * conventions. + * + * This implementation is very basic and does not maintain + * any statistics. The main point is to enforce the raw data + * and frame orientation of HDLC communications. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define HDLC_MAGIC 0x239e + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> + +#undef VERSION +#define VERSION(major,minor,patch) (((((major)<<8)+(minor))<<8)+(patch)) + +#include <linux/poll.h> +#include <linux/in.h> +#include <linux/ioctl.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/smp_lock.h> +#include <linux/string.h> /* used in new tty drivers */ +#include <linux/signal.h> /* used in new tty drivers */ +#include <linux/if.h> +#include <linux/bitops.h> + +#include <asm/system.h> +#include <asm/termios.h> +#include <asm/uaccess.h> + +/* + * Buffers for individual HDLC frames + */ +#define MAX_HDLC_FRAME_SIZE 65535 +#define DEFAULT_RX_BUF_COUNT 10 +#define MAX_RX_BUF_COUNT 60 +#define DEFAULT_TX_BUF_COUNT 3 + +struct n_hdlc_buf { + struct n_hdlc_buf *link; + int count; + char buf[1]; +}; + +#define N_HDLC_BUF_SIZE (sizeof(struct n_hdlc_buf) + maxframe) + +struct n_hdlc_buf_list { + struct n_hdlc_buf *head; + struct n_hdlc_buf *tail; + int count; + spinlock_t spinlock; +}; + +/** + * struct n_hdlc - per device instance data structure + * @magic - magic value for structure + * @flags - miscellaneous control flags + * @tty - ptr to TTY structure + * @backup_tty - TTY to use if tty gets closed + * @tbusy - reentrancy flag for tx wakeup code + * @woke_up - FIXME: describe this field + * @tbuf - currently transmitting tx buffer + * @tx_buf_list - list of pending transmit frame buffers + * @rx_buf_list - list of received frame buffers + * @tx_free_buf_list - list unused transmit frame buffers + * @rx_free_buf_list - list unused received frame buffers + */ +struct n_hdlc { + int magic; + __u32 flags; + struct tty_struct *tty; + struct tty_struct *backup_tty; + int tbusy; + int woke_up; + struct n_hdlc_buf *tbuf; + struct n_hdlc_buf_list tx_buf_list; + struct n_hdlc_buf_list rx_buf_list; + struct n_hdlc_buf_list tx_free_buf_list; + struct n_hdlc_buf_list rx_free_buf_list; +}; + +/* + * HDLC buffer list manipulation functions + */ +static void n_hdlc_buf_list_init(struct n_hdlc_buf_list *list); +static void n_hdlc_buf_put(struct n_hdlc_buf_list *list, + struct n_hdlc_buf *buf); +static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *list); + +/* Local functions */ + +static struct n_hdlc *n_hdlc_alloc (void); + +/* debug level can be set by insmod for debugging purposes */ +#define DEBUG_LEVEL_INFO 1 +static int debuglevel; + +/* max frame size for memory allocations */ +static int maxframe = 4096; + +/* TTY callbacks */ + +static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file, + __u8 __user *buf, size_t nr); +static ssize_t n_hdlc_tty_write(struct tty_struct *tty, struct file *file, + const unsigned char *buf, size_t nr); +static int n_hdlc_tty_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg); +static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp, + poll_table *wait); +static int n_hdlc_tty_open(struct tty_struct *tty); +static void n_hdlc_tty_close(struct tty_struct *tty); +static void n_hdlc_tty_receive(struct tty_struct *tty, const __u8 *cp, + char *fp, int count); +static void n_hdlc_tty_wakeup(struct tty_struct *tty); + +#define bset(p,b) ((p)[(b) >> 5] |= (1 << ((b) & 0x1f))) + +#define tty2n_hdlc(tty) ((struct n_hdlc *) ((tty)->disc_data)) +#define n_hdlc2tty(n_hdlc) ((n_hdlc)->tty) + +static void flush_rx_queue(struct tty_struct *tty) +{ + struct n_hdlc *n_hdlc = tty2n_hdlc(tty); + struct n_hdlc_buf *buf; + + while ((buf = n_hdlc_buf_get(&n_hdlc->rx_buf_list))) + n_hdlc_buf_put(&n_hdlc->rx_free_buf_list, buf); +} + +static void flush_tx_queue(struct tty_struct *tty) +{ + struct n_hdlc *n_hdlc = tty2n_hdlc(tty); + struct n_hdlc_buf *buf; + unsigned long flags; + + while ((buf = n_hdlc_buf_get(&n_hdlc->tx_buf_list))) + n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, buf); + spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); + if (n_hdlc->tbuf) { + n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, n_hdlc->tbuf); + n_hdlc->tbuf = NULL; + } + spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); +} + +static struct tty_ldisc_ops n_hdlc_ldisc = { + .owner = THIS_MODULE, + .magic = TTY_LDISC_MAGIC, + .name = "hdlc", + .open = n_hdlc_tty_open, + .close = n_hdlc_tty_close, + .read = n_hdlc_tty_read, + .write = n_hdlc_tty_write, + .ioctl = n_hdlc_tty_ioctl, + .poll = n_hdlc_tty_poll, + .receive_buf = n_hdlc_tty_receive, + .write_wakeup = n_hdlc_tty_wakeup, + .flush_buffer = flush_rx_queue, +}; + +/** + * n_hdlc_release - release an n_hdlc per device line discipline info structure + * @n_hdlc - per device line discipline info structure + */ +static void n_hdlc_release(struct n_hdlc *n_hdlc) +{ + struct tty_struct *tty = n_hdlc2tty (n_hdlc); + struct n_hdlc_buf *buf; + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_release() called\n",__FILE__,__LINE__); + + /* Ensure that the n_hdlcd process is not hanging on select()/poll() */ + wake_up_interruptible (&tty->read_wait); + wake_up_interruptible (&tty->write_wait); + + if (tty->disc_data == n_hdlc) + tty->disc_data = NULL; /* Break the tty->n_hdlc link */ + + /* Release transmit and receive buffers */ + for(;;) { + buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list); + if (buf) { + kfree(buf); + } else + break; + } + for(;;) { + buf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list); + if (buf) { + kfree(buf); + } else + break; + } + for(;;) { + buf = n_hdlc_buf_get(&n_hdlc->rx_buf_list); + if (buf) { + kfree(buf); + } else + break; + } + for(;;) { + buf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); + if (buf) { + kfree(buf); + } else + break; + } + kfree(n_hdlc->tbuf); + kfree(n_hdlc); + +} /* end of n_hdlc_release() */ + +/** + * n_hdlc_tty_close - line discipline close + * @tty - pointer to tty info structure + * + * Called when the line discipline is changed to something + * else, the tty is closed, or the tty detects a hangup. + */ +static void n_hdlc_tty_close(struct tty_struct *tty) +{ + struct n_hdlc *n_hdlc = tty2n_hdlc (tty); + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_close() called\n",__FILE__,__LINE__); + + if (n_hdlc != NULL) { + if (n_hdlc->magic != HDLC_MAGIC) { + printk (KERN_WARNING"n_hdlc: trying to close unopened tty!\n"); + return; + } +#if defined(TTY_NO_WRITE_SPLIT) + clear_bit(TTY_NO_WRITE_SPLIT,&tty->flags); +#endif + tty->disc_data = NULL; + if (tty == n_hdlc->backup_tty) + n_hdlc->backup_tty = NULL; + if (tty != n_hdlc->tty) + return; + if (n_hdlc->backup_tty) { + n_hdlc->tty = n_hdlc->backup_tty; + } else { + n_hdlc_release (n_hdlc); + } + } + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_close() success\n",__FILE__,__LINE__); + +} /* end of n_hdlc_tty_close() */ + +/** + * n_hdlc_tty_open - called when line discipline changed to n_hdlc + * @tty - pointer to tty info structure + * + * Returns 0 if success, otherwise error code + */ +static int n_hdlc_tty_open (struct tty_struct *tty) +{ + struct n_hdlc *n_hdlc = tty2n_hdlc (tty); + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_open() called (device=%s)\n", + __FILE__,__LINE__, + tty->name); + + /* There should not be an existing table for this slot. */ + if (n_hdlc) { + printk (KERN_ERR"n_hdlc_tty_open:tty already associated!\n" ); + return -EEXIST; + } + + n_hdlc = n_hdlc_alloc(); + if (!n_hdlc) { + printk (KERN_ERR "n_hdlc_alloc failed\n"); + return -ENFILE; + } + + tty->disc_data = n_hdlc; + n_hdlc->tty = tty; + tty->receive_room = 65536; + +#if defined(TTY_NO_WRITE_SPLIT) + /* change tty_io write() to not split large writes into 8K chunks */ + set_bit(TTY_NO_WRITE_SPLIT,&tty->flags); +#endif + + /* flush receive data from driver */ + tty_driver_flush_buffer(tty); + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_open() success\n",__FILE__,__LINE__); + + return 0; + +} /* end of n_tty_hdlc_open() */ + +/** + * n_hdlc_send_frames - send frames on pending send buffer list + * @n_hdlc - pointer to ldisc instance data + * @tty - pointer to tty instance data + * + * Send frames on pending send buffer list until the driver does not accept a + * frame (busy) this function is called after adding a frame to the send buffer + * list and by the tty wakeup callback. + */ +static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty) +{ + register int actual; + unsigned long flags; + struct n_hdlc_buf *tbuf; + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_send_frames() called\n",__FILE__,__LINE__); + check_again: + + spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); + if (n_hdlc->tbusy) { + n_hdlc->woke_up = 1; + spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); + return; + } + n_hdlc->tbusy = 1; + n_hdlc->woke_up = 0; + spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); + + /* get current transmit buffer or get new transmit */ + /* buffer from list of pending transmit buffers */ + + tbuf = n_hdlc->tbuf; + if (!tbuf) + tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); + + while (tbuf) { + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)sending frame %p, count=%d\n", + __FILE__,__LINE__,tbuf,tbuf->count); + + /* Send the next block of data to device */ + tty->flags |= (1 << TTY_DO_WRITE_WAKEUP); + actual = tty->ops->write(tty, tbuf->buf, tbuf->count); + + /* rollback was possible and has been done */ + if (actual == -ERESTARTSYS) { + n_hdlc->tbuf = tbuf; + break; + } + /* if transmit error, throw frame away by */ + /* pretending it was accepted by driver */ + if (actual < 0) + actual = tbuf->count; + + if (actual == tbuf->count) { + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)frame %p completed\n", + __FILE__,__LINE__,tbuf); + + /* free current transmit buffer */ + n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, tbuf); + + /* this tx buffer is done */ + n_hdlc->tbuf = NULL; + + /* wait up sleeping writers */ + wake_up_interruptible(&tty->write_wait); + + /* get next pending transmit buffer */ + tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); + } else { + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)frame %p pending\n", + __FILE__,__LINE__,tbuf); + + /* buffer not accepted by driver */ + /* set this buffer as pending buffer */ + n_hdlc->tbuf = tbuf; + break; + } + } + + if (!tbuf) + tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + + /* Clear the re-entry flag */ + spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); + n_hdlc->tbusy = 0; + spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); + + if (n_hdlc->woke_up) + goto check_again; + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_send_frames() exit\n",__FILE__,__LINE__); + +} /* end of n_hdlc_send_frames() */ + +/** + * n_hdlc_tty_wakeup - Callback for transmit wakeup + * @tty - pointer to associated tty instance data + * + * Called when low level device driver can accept more send data. + */ +static void n_hdlc_tty_wakeup(struct tty_struct *tty) +{ + struct n_hdlc *n_hdlc = tty2n_hdlc(tty); + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_wakeup() called\n",__FILE__,__LINE__); + + if (!n_hdlc) + return; + + if (tty != n_hdlc->tty) { + tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + return; + } + + n_hdlc_send_frames (n_hdlc, tty); + +} /* end of n_hdlc_tty_wakeup() */ + +/** + * n_hdlc_tty_receive - Called by tty driver when receive data is available + * @tty - pointer to tty instance data + * @data - pointer to received data + * @flags - pointer to flags for data + * @count - count of received data in bytes + * + * Called by tty low level driver when receive data is available. Data is + * interpreted as one HDLC frame. + */ +static void n_hdlc_tty_receive(struct tty_struct *tty, const __u8 *data, + char *flags, int count) +{ + register struct n_hdlc *n_hdlc = tty2n_hdlc (tty); + register struct n_hdlc_buf *buf; + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_receive() called count=%d\n", + __FILE__,__LINE__, count); + + /* This can happen if stuff comes in on the backup tty */ + if (!n_hdlc || tty != n_hdlc->tty) + return; + + /* verify line is using HDLC discipline */ + if (n_hdlc->magic != HDLC_MAGIC) { + printk("%s(%d) line not using HDLC discipline\n", + __FILE__,__LINE__); + return; + } + + if ( count>maxframe ) { + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d) rx count>maxframesize, data discarded\n", + __FILE__,__LINE__); + return; + } + + /* get a free HDLC buffer */ + buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list); + if (!buf) { + /* no buffers in free list, attempt to allocate another rx buffer */ + /* unless the maximum count has been reached */ + if (n_hdlc->rx_buf_list.count < MAX_RX_BUF_COUNT) + buf = kmalloc(N_HDLC_BUF_SIZE, GFP_ATOMIC); + } + + if (!buf) { + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d) no more rx buffers, data discarded\n", + __FILE__,__LINE__); + return; + } + + /* copy received data to HDLC buffer */ + memcpy(buf->buf,data,count); + buf->count=count; + + /* add HDLC buffer to list of received frames */ + n_hdlc_buf_put(&n_hdlc->rx_buf_list, buf); + + /* wake up any blocked reads and perform async signalling */ + wake_up_interruptible (&tty->read_wait); + if (n_hdlc->tty->fasync != NULL) + kill_fasync (&n_hdlc->tty->fasync, SIGIO, POLL_IN); + +} /* end of n_hdlc_tty_receive() */ + +/** + * n_hdlc_tty_read - Called to retrieve one frame of data (if available) + * @tty - pointer to tty instance data + * @file - pointer to open file object + * @buf - pointer to returned data buffer + * @nr - size of returned data buffer + * + * Returns the number of bytes returned or error code. + */ +static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file, + __u8 __user *buf, size_t nr) +{ + struct n_hdlc *n_hdlc = tty2n_hdlc(tty); + int ret; + struct n_hdlc_buf *rbuf; + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_read() called\n",__FILE__,__LINE__); + + /* Validate the pointers */ + if (!n_hdlc) + return -EIO; + + /* verify user access to buffer */ + if (!access_ok(VERIFY_WRITE, buf, nr)) { + printk(KERN_WARNING "%s(%d) n_hdlc_tty_read() can't verify user " + "buffer\n", __FILE__, __LINE__); + return -EFAULT; + } + + tty_lock(); + + for (;;) { + if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) { + tty_unlock(); + return -EIO; + } + + n_hdlc = tty2n_hdlc (tty); + if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC || + tty != n_hdlc->tty) { + tty_unlock(); + return 0; + } + + rbuf = n_hdlc_buf_get(&n_hdlc->rx_buf_list); + if (rbuf) + break; + + /* no data */ + if (file->f_flags & O_NONBLOCK) { + tty_unlock(); + return -EAGAIN; + } + + interruptible_sleep_on (&tty->read_wait); + if (signal_pending(current)) { + tty_unlock(); + return -EINTR; + } + } + + if (rbuf->count > nr) + /* frame too large for caller's buffer (discard frame) */ + ret = -EOVERFLOW; + else { + /* Copy the data to the caller's buffer */ + if (copy_to_user(buf, rbuf->buf, rbuf->count)) + ret = -EFAULT; + else + ret = rbuf->count; + } + + /* return HDLC buffer to free list unless the free list */ + /* count has exceeded the default value, in which case the */ + /* buffer is freed back to the OS to conserve memory */ + if (n_hdlc->rx_free_buf_list.count > DEFAULT_RX_BUF_COUNT) + kfree(rbuf); + else + n_hdlc_buf_put(&n_hdlc->rx_free_buf_list,rbuf); + tty_unlock(); + return ret; + +} /* end of n_hdlc_tty_read() */ + +/** + * n_hdlc_tty_write - write a single frame of data to device + * @tty - pointer to associated tty device instance data + * @file - pointer to file object data + * @data - pointer to transmit data (one frame) + * @count - size of transmit frame in bytes + * + * Returns the number of bytes written (or error code). + */ +static ssize_t n_hdlc_tty_write(struct tty_struct *tty, struct file *file, + const unsigned char *data, size_t count) +{ + struct n_hdlc *n_hdlc = tty2n_hdlc (tty); + int error = 0; + DECLARE_WAITQUEUE(wait, current); + struct n_hdlc_buf *tbuf; + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_write() called count=%Zd\n", + __FILE__,__LINE__,count); + + /* Verify pointers */ + if (!n_hdlc) + return -EIO; + + if (n_hdlc->magic != HDLC_MAGIC) + return -EIO; + + /* verify frame size */ + if (count > maxframe ) { + if (debuglevel & DEBUG_LEVEL_INFO) + printk (KERN_WARNING + "n_hdlc_tty_write: truncating user packet " + "from %lu to %d\n", (unsigned long) count, + maxframe ); + count = maxframe; + } + + tty_lock(); + + add_wait_queue(&tty->write_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + /* Allocate transmit buffer */ + /* sleep until transmit buffer available */ + while (!(tbuf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list))) { + if (file->f_flags & O_NONBLOCK) { + error = -EAGAIN; + break; + } + schedule(); + + n_hdlc = tty2n_hdlc (tty); + if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC || + tty != n_hdlc->tty) { + printk("n_hdlc_tty_write: %p invalid after wait!\n", n_hdlc); + error = -EIO; + break; + } + + if (signal_pending(current)) { + error = -EINTR; + break; + } + } + + set_current_state(TASK_RUNNING); + remove_wait_queue(&tty->write_wait, &wait); + + if (!error) { + /* Retrieve the user's buffer */ + memcpy(tbuf->buf, data, count); + + /* Send the data */ + tbuf->count = error = count; + n_hdlc_buf_put(&n_hdlc->tx_buf_list,tbuf); + n_hdlc_send_frames(n_hdlc,tty); + } + tty_unlock(); + return error; + +} /* end of n_hdlc_tty_write() */ + +/** + * n_hdlc_tty_ioctl - process IOCTL system call for the tty device. + * @tty - pointer to tty instance data + * @file - pointer to open file object for device + * @cmd - IOCTL command code + * @arg - argument for IOCTL call (cmd dependent) + * + * Returns command dependent result. + */ +static int n_hdlc_tty_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct n_hdlc *n_hdlc = tty2n_hdlc (tty); + int error = 0; + int count; + unsigned long flags; + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_ioctl() called %d\n", + __FILE__,__LINE__,cmd); + + /* Verify the status of the device */ + if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC) + return -EBADF; + + switch (cmd) { + case FIONREAD: + /* report count of read data available */ + /* in next available frame (if any) */ + spin_lock_irqsave(&n_hdlc->rx_buf_list.spinlock,flags); + if (n_hdlc->rx_buf_list.head) + count = n_hdlc->rx_buf_list.head->count; + else + count = 0; + spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock,flags); + error = put_user(count, (int __user *)arg); + break; + + case TIOCOUTQ: + /* get the pending tx byte count in the driver */ + count = tty_chars_in_buffer(tty); + /* add size of next output frame in queue */ + spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock,flags); + if (n_hdlc->tx_buf_list.head) + count += n_hdlc->tx_buf_list.head->count; + spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock,flags); + error = put_user(count, (int __user *)arg); + break; + + case TCFLSH: + switch (arg) { + case TCIOFLUSH: + case TCOFLUSH: + flush_tx_queue(tty); + } + /* fall through to default */ + + default: + error = n_tty_ioctl_helper(tty, file, cmd, arg); + break; + } + return error; + +} /* end of n_hdlc_tty_ioctl() */ + +/** + * n_hdlc_tty_poll - TTY callback for poll system call + * @tty - pointer to tty instance data + * @filp - pointer to open file object for device + * @poll_table - wait queue for operations + * + * Determine which operations (read/write) will not block and return info + * to caller. + * Returns a bit mask containing info on which ops will not block. + */ +static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp, + poll_table *wait) +{ + struct n_hdlc *n_hdlc = tty2n_hdlc (tty); + unsigned int mask = 0; + + if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_tty_poll() called\n",__FILE__,__LINE__); + + if (n_hdlc && n_hdlc->magic == HDLC_MAGIC && tty == n_hdlc->tty) { + /* queue current process into any wait queue that */ + /* may awaken in the future (read and write) */ + + poll_wait(filp, &tty->read_wait, wait); + poll_wait(filp, &tty->write_wait, wait); + + /* set bits for operations that won't block */ + if (n_hdlc->rx_buf_list.head) + mask |= POLLIN | POLLRDNORM; /* readable */ + if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) + mask |= POLLHUP; + if (tty_hung_up_p(filp)) + mask |= POLLHUP; + if (!tty_is_writelocked(tty) && + n_hdlc->tx_free_buf_list.head) + mask |= POLLOUT | POLLWRNORM; /* writable */ + } + return mask; +} /* end of n_hdlc_tty_poll() */ + +/** + * n_hdlc_alloc - allocate an n_hdlc instance data structure + * + * Returns a pointer to newly created structure if success, otherwise %NULL + */ +static struct n_hdlc *n_hdlc_alloc(void) +{ + struct n_hdlc_buf *buf; + int i; + struct n_hdlc *n_hdlc = kmalloc(sizeof(*n_hdlc), GFP_KERNEL); + + if (!n_hdlc) + return NULL; + + memset(n_hdlc, 0, sizeof(*n_hdlc)); + + n_hdlc_buf_list_init(&n_hdlc->rx_free_buf_list); + n_hdlc_buf_list_init(&n_hdlc->tx_free_buf_list); + n_hdlc_buf_list_init(&n_hdlc->rx_buf_list); + n_hdlc_buf_list_init(&n_hdlc->tx_buf_list); + + /* allocate free rx buffer list */ + for(i=0;i<DEFAULT_RX_BUF_COUNT;i++) { + buf = kmalloc(N_HDLC_BUF_SIZE, GFP_KERNEL); + if (buf) + n_hdlc_buf_put(&n_hdlc->rx_free_buf_list,buf); + else if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_alloc(), kalloc() failed for rx buffer %d\n",__FILE__,__LINE__, i); + } + + /* allocate free tx buffer list */ + for(i=0;i<DEFAULT_TX_BUF_COUNT;i++) { + buf = kmalloc(N_HDLC_BUF_SIZE, GFP_KERNEL); + if (buf) + n_hdlc_buf_put(&n_hdlc->tx_free_buf_list,buf); + else if (debuglevel >= DEBUG_LEVEL_INFO) + printk("%s(%d)n_hdlc_alloc(), kalloc() failed for tx buffer %d\n",__FILE__,__LINE__, i); + } + + /* Initialize the control block */ + n_hdlc->magic = HDLC_MAGIC; + n_hdlc->flags = 0; + + return n_hdlc; + +} /* end of n_hdlc_alloc() */ + +/** + * n_hdlc_buf_list_init - initialize specified HDLC buffer list + * @list - pointer to buffer list + */ +static void n_hdlc_buf_list_init(struct n_hdlc_buf_list *list) +{ + memset(list, 0, sizeof(*list)); + spin_lock_init(&list->spinlock); +} /* end of n_hdlc_buf_list_init() */ + +/** + * n_hdlc_buf_put - add specified HDLC buffer to tail of specified list + * @list - pointer to buffer list + * @buf - pointer to buffer + */ +static void n_hdlc_buf_put(struct n_hdlc_buf_list *list, + struct n_hdlc_buf *buf) +{ + unsigned long flags; + spin_lock_irqsave(&list->spinlock,flags); + + buf->link=NULL; + if (list->tail) + list->tail->link = buf; + else + list->head = buf; + list->tail = buf; + (list->count)++; + + spin_unlock_irqrestore(&list->spinlock,flags); + +} /* end of n_hdlc_buf_put() */ + +/** + * n_hdlc_buf_get - remove and return an HDLC buffer from list + * @list - pointer to HDLC buffer list + * + * Remove and return an HDLC buffer from the head of the specified HDLC buffer + * list. + * Returns a pointer to HDLC buffer if available, otherwise %NULL. + */ +static struct n_hdlc_buf* n_hdlc_buf_get(struct n_hdlc_buf_list *list) +{ + unsigned long flags; + struct n_hdlc_buf *buf; + spin_lock_irqsave(&list->spinlock,flags); + + buf = list->head; + if (buf) { + list->head = buf->link; + (list->count)--; + } + if (!list->head) + list->tail = NULL; + + spin_unlock_irqrestore(&list->spinlock,flags); + return buf; + +} /* end of n_hdlc_buf_get() */ + +static char hdlc_banner[] __initdata = + KERN_INFO "HDLC line discipline maxframe=%u\n"; +static char hdlc_register_ok[] __initdata = + KERN_INFO "N_HDLC line discipline registered.\n"; +static char hdlc_register_fail[] __initdata = + KERN_ERR "error registering line discipline: %d\n"; +static char hdlc_init_fail[] __initdata = + KERN_INFO "N_HDLC: init failure %d\n"; + +static int __init n_hdlc_init(void) +{ + int status; + + /* range check maxframe arg */ + if (maxframe < 4096) + maxframe = 4096; + else if (maxframe > 65535) + maxframe = 65535; + + printk(hdlc_banner, maxframe); + + status = tty_register_ldisc(N_HDLC, &n_hdlc_ldisc); + if (!status) + printk(hdlc_register_ok); + else + printk(hdlc_register_fail, status); + + if (status) + printk(hdlc_init_fail, status); + return status; + +} /* end of init_module() */ + +static char hdlc_unregister_ok[] __exitdata = + KERN_INFO "N_HDLC: line discipline unregistered\n"; +static char hdlc_unregister_fail[] __exitdata = + KERN_ERR "N_HDLC: can't unregister line discipline (err = %d)\n"; + +static void __exit n_hdlc_exit(void) +{ + /* Release tty registration of line discipline */ + int status = tty_unregister_ldisc(N_HDLC); + + if (status) + printk(hdlc_unregister_fail, status); + else + printk(hdlc_unregister_ok); +} + +module_init(n_hdlc_init); +module_exit(n_hdlc_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Paul Fulghum paulkf@microgate.com"); +module_param(debuglevel, int, 0); +module_param(maxframe, int, 0); +MODULE_ALIAS_LDISC(N_HDLC); diff --git a/drivers/tty/n_r3964.c b/drivers/tty/n_r3964.c new file mode 100644 index 000000000000..88dda0c45ee0 --- /dev/null +++ b/drivers/tty/n_r3964.c @@ -0,0 +1,1264 @@ +/* r3964 linediscipline for linux + * + * ----------------------------------------------------------- + * Copyright by + * Philips Automation Projects + * Kassel (Germany) + * ----------------------------------------------------------- + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + * + * Author: + * L. Haag + * + * $Log: n_r3964.c,v $ + * Revision 1.10 2001/03/18 13:02:24 dwmw2 + * Fix timer usage, use spinlocks properly. + * + * Revision 1.9 2001/03/18 12:52:14 dwmw2 + * Merge changes in 2.4.2 + * + * Revision 1.8 2000/03/23 14:14:54 dwmw2 + * Fix race in sleeping in r3964_read() + * + * Revision 1.7 1999/28/08 11:41:50 dwmw2 + * Port to 2.3 kernel + * + * Revision 1.6 1998/09/30 00:40:40 dwmw2 + * Fixed compilation on 2.0.x kernels + * Updated to newly registered tty-ldisc number 9 + * + * Revision 1.5 1998/09/04 21:57:36 dwmw2 + * Signal handling bug fixes, port to 2.1.x. + * + * Revision 1.4 1998/04/02 20:26:59 lhaag + * select, blocking, ... + * + * Revision 1.3 1998/02/12 18:58:43 root + * fixed some memory leaks + * calculation of checksum characters + * + * Revision 1.2 1998/02/07 13:03:34 root + * ioctl read_telegram + * + * Revision 1.1 1998/02/06 19:21:03 root + * Initial revision + * + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> /* used in new tty drivers */ +#include <linux/signal.h> /* used in new tty drivers */ +#include <linux/ioctl.h> +#include <linux/n_r3964.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <asm/uaccess.h> + +/*#define DEBUG_QUEUE*/ + +/* Log successful handshake and protocol operations */ +/*#define DEBUG_PROTO_S*/ + +/* Log handshake and protocol errors: */ +/*#define DEBUG_PROTO_E*/ + +/* Log Linediscipline operations (open, close, read, write...): */ +/*#define DEBUG_LDISC*/ + +/* Log module and memory operations (init, cleanup; kmalloc, kfree): */ +/*#define DEBUG_MODUL*/ + +/* Macro helpers for debug output: */ +#define TRACE(format, args...) printk("r3964: " format "\n" , ## args) + +#ifdef DEBUG_MODUL +#define TRACE_M(format, args...) printk("r3964: " format "\n" , ## args) +#else +#define TRACE_M(fmt, arg...) do {} while (0) +#endif +#ifdef DEBUG_PROTO_S +#define TRACE_PS(format, args...) printk("r3964: " format "\n" , ## args) +#else +#define TRACE_PS(fmt, arg...) do {} while (0) +#endif +#ifdef DEBUG_PROTO_E +#define TRACE_PE(format, args...) printk("r3964: " format "\n" , ## args) +#else +#define TRACE_PE(fmt, arg...) do {} while (0) +#endif +#ifdef DEBUG_LDISC +#define TRACE_L(format, args...) printk("r3964: " format "\n" , ## args) +#else +#define TRACE_L(fmt, arg...) do {} while (0) +#endif +#ifdef DEBUG_QUEUE +#define TRACE_Q(format, args...) printk("r3964: " format "\n" , ## args) +#else +#define TRACE_Q(fmt, arg...) do {} while (0) +#endif +static void add_tx_queue(struct r3964_info *, struct r3964_block_header *); +static void remove_from_tx_queue(struct r3964_info *pInfo, int error_code); +static void put_char(struct r3964_info *pInfo, unsigned char ch); +static void trigger_transmit(struct r3964_info *pInfo); +static void retry_transmit(struct r3964_info *pInfo); +static void transmit_block(struct r3964_info *pInfo); +static void receive_char(struct r3964_info *pInfo, const unsigned char c); +static void receive_error(struct r3964_info *pInfo, const char flag); +static void on_timeout(unsigned long priv); +static int enable_signals(struct r3964_info *pInfo, struct pid *pid, int arg); +static int read_telegram(struct r3964_info *pInfo, struct pid *pid, + unsigned char __user * buf); +static void add_msg(struct r3964_client_info *pClient, int msg_id, int arg, + int error_code, struct r3964_block_header *pBlock); +static struct r3964_message *remove_msg(struct r3964_info *pInfo, + struct r3964_client_info *pClient); +static void remove_client_block(struct r3964_info *pInfo, + struct r3964_client_info *pClient); + +static int r3964_open(struct tty_struct *tty); +static void r3964_close(struct tty_struct *tty); +static ssize_t r3964_read(struct tty_struct *tty, struct file *file, + unsigned char __user * buf, size_t nr); +static ssize_t r3964_write(struct tty_struct *tty, struct file *file, + const unsigned char *buf, size_t nr); +static int r3964_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg); +static void r3964_set_termios(struct tty_struct *tty, struct ktermios *old); +static unsigned int r3964_poll(struct tty_struct *tty, struct file *file, + struct poll_table_struct *wait); +static void r3964_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count); + +static struct tty_ldisc_ops tty_ldisc_N_R3964 = { + .owner = THIS_MODULE, + .magic = TTY_LDISC_MAGIC, + .name = "R3964", + .open = r3964_open, + .close = r3964_close, + .read = r3964_read, + .write = r3964_write, + .ioctl = r3964_ioctl, + .set_termios = r3964_set_termios, + .poll = r3964_poll, + .receive_buf = r3964_receive_buf, +}; + +static void dump_block(const unsigned char *block, unsigned int length) +{ + unsigned int i, j; + char linebuf[16 * 3 + 1]; + + for (i = 0; i < length; i += 16) { + for (j = 0; (j < 16) && (j + i < length); j++) { + sprintf(linebuf + 3 * j, "%02x ", block[i + j]); + } + linebuf[3 * j] = '\0'; + TRACE_PS("%s", linebuf); + } +} + +/************************************************************* + * Driver initialisation + *************************************************************/ + +/************************************************************* + * Module support routines + *************************************************************/ + +static void __exit r3964_exit(void) +{ + int status; + + TRACE_M("cleanup_module()"); + + status = tty_unregister_ldisc(N_R3964); + + if (status != 0) { + printk(KERN_ERR "r3964: error unregistering linediscipline: " + "%d\n", status); + } else { + TRACE_L("linediscipline successfully unregistered"); + } +} + +static int __init r3964_init(void) +{ + int status; + + printk("r3964: Philips r3964 Driver $Revision: 1.10 $\n"); + + /* + * Register the tty line discipline + */ + + status = tty_register_ldisc(N_R3964, &tty_ldisc_N_R3964); + if (status == 0) { + TRACE_L("line discipline %d registered", N_R3964); + TRACE_L("flags=%x num=%x", tty_ldisc_N_R3964.flags, + tty_ldisc_N_R3964.num); + TRACE_L("open=%p", tty_ldisc_N_R3964.open); + TRACE_L("tty_ldisc_N_R3964 = %p", &tty_ldisc_N_R3964); + } else { + printk(KERN_ERR "r3964: error registering line discipline: " + "%d\n", status); + } + return status; +} + +module_init(r3964_init); +module_exit(r3964_exit); + +/************************************************************* + * Protocol implementation routines + *************************************************************/ + +static void add_tx_queue(struct r3964_info *pInfo, + struct r3964_block_header *pHeader) +{ + unsigned long flags; + + spin_lock_irqsave(&pInfo->lock, flags); + + pHeader->next = NULL; + + if (pInfo->tx_last == NULL) { + pInfo->tx_first = pInfo->tx_last = pHeader; + } else { + pInfo->tx_last->next = pHeader; + pInfo->tx_last = pHeader; + } + + spin_unlock_irqrestore(&pInfo->lock, flags); + + TRACE_Q("add_tx_queue %p, length %d, tx_first = %p", + pHeader, pHeader->length, pInfo->tx_first); +} + +static void remove_from_tx_queue(struct r3964_info *pInfo, int error_code) +{ + struct r3964_block_header *pHeader; + unsigned long flags; +#ifdef DEBUG_QUEUE + struct r3964_block_header *pDump; +#endif + + pHeader = pInfo->tx_first; + + if (pHeader == NULL) + return; + +#ifdef DEBUG_QUEUE + printk("r3964: remove_from_tx_queue: %p, length %u - ", + pHeader, pHeader->length); + for (pDump = pHeader; pDump; pDump = pDump->next) + printk("%p ", pDump); + printk("\n"); +#endif + + if (pHeader->owner) { + if (error_code) { + add_msg(pHeader->owner, R3964_MSG_ACK, 0, + error_code, NULL); + } else { + add_msg(pHeader->owner, R3964_MSG_ACK, pHeader->length, + error_code, NULL); + } + wake_up_interruptible(&pInfo->read_wait); + } + + spin_lock_irqsave(&pInfo->lock, flags); + + pInfo->tx_first = pHeader->next; + if (pInfo->tx_first == NULL) { + pInfo->tx_last = NULL; + } + + spin_unlock_irqrestore(&pInfo->lock, flags); + + kfree(pHeader); + TRACE_M("remove_from_tx_queue - kfree %p", pHeader); + + TRACE_Q("remove_from_tx_queue: tx_first = %p, tx_last = %p", + pInfo->tx_first, pInfo->tx_last); +} + +static void add_rx_queue(struct r3964_info *pInfo, + struct r3964_block_header *pHeader) +{ + unsigned long flags; + + spin_lock_irqsave(&pInfo->lock, flags); + + pHeader->next = NULL; + + if (pInfo->rx_last == NULL) { + pInfo->rx_first = pInfo->rx_last = pHeader; + } else { + pInfo->rx_last->next = pHeader; + pInfo->rx_last = pHeader; + } + pInfo->blocks_in_rx_queue++; + + spin_unlock_irqrestore(&pInfo->lock, flags); + + TRACE_Q("add_rx_queue: %p, length = %d, rx_first = %p, count = %d", + pHeader, pHeader->length, + pInfo->rx_first, pInfo->blocks_in_rx_queue); +} + +static void remove_from_rx_queue(struct r3964_info *pInfo, + struct r3964_block_header *pHeader) +{ + unsigned long flags; + struct r3964_block_header *pFind; + + if (pHeader == NULL) + return; + + TRACE_Q("remove_from_rx_queue: rx_first = %p, rx_last = %p, count = %d", + pInfo->rx_first, pInfo->rx_last, pInfo->blocks_in_rx_queue); + TRACE_Q("remove_from_rx_queue: %p, length %u", + pHeader, pHeader->length); + + spin_lock_irqsave(&pInfo->lock, flags); + + if (pInfo->rx_first == pHeader) { + /* Remove the first block in the linked list: */ + pInfo->rx_first = pHeader->next; + + if (pInfo->rx_first == NULL) { + pInfo->rx_last = NULL; + } + pInfo->blocks_in_rx_queue--; + } else { + /* Find block to remove: */ + for (pFind = pInfo->rx_first; pFind; pFind = pFind->next) { + if (pFind->next == pHeader) { + /* Got it. */ + pFind->next = pHeader->next; + pInfo->blocks_in_rx_queue--; + if (pFind->next == NULL) { + /* Oh, removed the last one! */ + pInfo->rx_last = pFind; + } + break; + } + } + } + + spin_unlock_irqrestore(&pInfo->lock, flags); + + kfree(pHeader); + TRACE_M("remove_from_rx_queue - kfree %p", pHeader); + + TRACE_Q("remove_from_rx_queue: rx_first = %p, rx_last = %p, count = %d", + pInfo->rx_first, pInfo->rx_last, pInfo->blocks_in_rx_queue); +} + +static void put_char(struct r3964_info *pInfo, unsigned char ch) +{ + struct tty_struct *tty = pInfo->tty; + /* FIXME: put_char should not be called from an IRQ */ + tty_put_char(tty, ch); + pInfo->bcc ^= ch; +} + +static void flush(struct r3964_info *pInfo) +{ + struct tty_struct *tty = pInfo->tty; + + if (tty == NULL || tty->ops->flush_chars == NULL) + return; + tty->ops->flush_chars(tty); +} + +static void trigger_transmit(struct r3964_info *pInfo) +{ + unsigned long flags; + + spin_lock_irqsave(&pInfo->lock, flags); + + if ((pInfo->state == R3964_IDLE) && (pInfo->tx_first != NULL)) { + pInfo->state = R3964_TX_REQUEST; + pInfo->nRetry = 0; + pInfo->flags &= ~R3964_ERROR; + mod_timer(&pInfo->tmr, jiffies + R3964_TO_QVZ); + + spin_unlock_irqrestore(&pInfo->lock, flags); + + TRACE_PS("trigger_transmit - sent STX"); + + put_char(pInfo, STX); + flush(pInfo); + + pInfo->bcc = 0; + } else { + spin_unlock_irqrestore(&pInfo->lock, flags); + } +} + +static void retry_transmit(struct r3964_info *pInfo) +{ + if (pInfo->nRetry < R3964_MAX_RETRIES) { + TRACE_PE("transmission failed. Retry #%d", pInfo->nRetry); + pInfo->bcc = 0; + put_char(pInfo, STX); + flush(pInfo); + pInfo->state = R3964_TX_REQUEST; + pInfo->nRetry++; + mod_timer(&pInfo->tmr, jiffies + R3964_TO_QVZ); + } else { + TRACE_PE("transmission failed after %d retries", + R3964_MAX_RETRIES); + + remove_from_tx_queue(pInfo, R3964_TX_FAIL); + + put_char(pInfo, NAK); + flush(pInfo); + pInfo->state = R3964_IDLE; + + trigger_transmit(pInfo); + } +} + +static void transmit_block(struct r3964_info *pInfo) +{ + struct tty_struct *tty = pInfo->tty; + struct r3964_block_header *pBlock = pInfo->tx_first; + int room = 0; + + if (tty == NULL || pBlock == NULL) { + return; + } + + room = tty_write_room(tty); + + TRACE_PS("transmit_block %p, room %d, length %d", + pBlock, room, pBlock->length); + + while (pInfo->tx_position < pBlock->length) { + if (room < 2) + break; + + if (pBlock->data[pInfo->tx_position] == DLE) { + /* send additional DLE char: */ + put_char(pInfo, DLE); + } + put_char(pInfo, pBlock->data[pInfo->tx_position++]); + + room--; + } + + if ((pInfo->tx_position == pBlock->length) && (room >= 3)) { + put_char(pInfo, DLE); + put_char(pInfo, ETX); + if (pInfo->flags & R3964_BCC) { + put_char(pInfo, pInfo->bcc); + } + pInfo->state = R3964_WAIT_FOR_TX_ACK; + mod_timer(&pInfo->tmr, jiffies + R3964_TO_QVZ); + } + flush(pInfo); +} + +static void on_receive_block(struct r3964_info *pInfo) +{ + unsigned int length; + struct r3964_client_info *pClient; + struct r3964_block_header *pBlock; + + length = pInfo->rx_position; + + /* compare byte checksum characters: */ + if (pInfo->flags & R3964_BCC) { + if (pInfo->bcc != pInfo->last_rx) { + TRACE_PE("checksum error - got %x but expected %x", + pInfo->last_rx, pInfo->bcc); + pInfo->flags |= R3964_CHECKSUM; + } + } + + /* check for errors (parity, overrun,...): */ + if (pInfo->flags & R3964_ERROR) { + TRACE_PE("on_receive_block - transmission failed error %x", + pInfo->flags & R3964_ERROR); + + put_char(pInfo, NAK); + flush(pInfo); + if (pInfo->nRetry < R3964_MAX_RETRIES) { + pInfo->state = R3964_WAIT_FOR_RX_REPEAT; + pInfo->nRetry++; + mod_timer(&pInfo->tmr, jiffies + R3964_TO_RX_PANIC); + } else { + TRACE_PE("on_receive_block - failed after max retries"); + pInfo->state = R3964_IDLE; + } + return; + } + + /* received block; submit DLE: */ + put_char(pInfo, DLE); + flush(pInfo); + del_timer_sync(&pInfo->tmr); + TRACE_PS(" rx success: got %d chars", length); + + /* prepare struct r3964_block_header: */ + pBlock = kmalloc(length + sizeof(struct r3964_block_header), + GFP_KERNEL); + TRACE_M("on_receive_block - kmalloc %p", pBlock); + + if (pBlock == NULL) + return; + + pBlock->length = length; + pBlock->data = ((unsigned char *)pBlock) + + sizeof(struct r3964_block_header); + pBlock->locks = 0; + pBlock->next = NULL; + pBlock->owner = NULL; + + memcpy(pBlock->data, pInfo->rx_buf, length); + + /* queue block into rx_queue: */ + add_rx_queue(pInfo, pBlock); + + /* notify attached client processes: */ + for (pClient = pInfo->firstClient; pClient; pClient = pClient->next) { + if (pClient->sig_flags & R3964_SIG_DATA) { + add_msg(pClient, R3964_MSG_DATA, length, R3964_OK, + pBlock); + } + } + wake_up_interruptible(&pInfo->read_wait); + + pInfo->state = R3964_IDLE; + + trigger_transmit(pInfo); +} + +static void receive_char(struct r3964_info *pInfo, const unsigned char c) +{ + switch (pInfo->state) { + case R3964_TX_REQUEST: + if (c == DLE) { + TRACE_PS("TX_REQUEST - got DLE"); + + pInfo->state = R3964_TRANSMITTING; + pInfo->tx_position = 0; + + transmit_block(pInfo); + } else if (c == STX) { + if (pInfo->nRetry == 0) { + TRACE_PE("TX_REQUEST - init conflict"); + if (pInfo->priority == R3964_SLAVE) { + goto start_receiving; + } + } else { + TRACE_PE("TX_REQUEST - secondary init " + "conflict!? Switching to SLAVE mode " + "for next rx."); + goto start_receiving; + } + } else { + TRACE_PE("TX_REQUEST - char != DLE: %x", c); + retry_transmit(pInfo); + } + break; + case R3964_TRANSMITTING: + if (c == NAK) { + TRACE_PE("TRANSMITTING - got NAK"); + retry_transmit(pInfo); + } else { + TRACE_PE("TRANSMITTING - got invalid char"); + + pInfo->state = R3964_WAIT_ZVZ_BEFORE_TX_RETRY; + mod_timer(&pInfo->tmr, jiffies + R3964_TO_ZVZ); + } + break; + case R3964_WAIT_FOR_TX_ACK: + if (c == DLE) { + TRACE_PS("WAIT_FOR_TX_ACK - got DLE"); + remove_from_tx_queue(pInfo, R3964_OK); + + pInfo->state = R3964_IDLE; + trigger_transmit(pInfo); + } else { + retry_transmit(pInfo); + } + break; + case R3964_WAIT_FOR_RX_REPEAT: + /* FALLTHROUGH */ + case R3964_IDLE: + if (c == STX) { + /* Prevent rx_queue from overflow: */ + if (pInfo->blocks_in_rx_queue >= + R3964_MAX_BLOCKS_IN_RX_QUEUE) { + TRACE_PE("IDLE - got STX but no space in " + "rx_queue!"); + pInfo->state = R3964_WAIT_FOR_RX_BUF; + mod_timer(&pInfo->tmr, + jiffies + R3964_TO_NO_BUF); + break; + } +start_receiving: + /* Ok, start receiving: */ + TRACE_PS("IDLE - got STX"); + pInfo->rx_position = 0; + pInfo->last_rx = 0; + pInfo->flags &= ~R3964_ERROR; + pInfo->state = R3964_RECEIVING; + mod_timer(&pInfo->tmr, jiffies + R3964_TO_ZVZ); + pInfo->nRetry = 0; + put_char(pInfo, DLE); + flush(pInfo); + pInfo->bcc = 0; + } + break; + case R3964_RECEIVING: + if (pInfo->rx_position < RX_BUF_SIZE) { + pInfo->bcc ^= c; + + if (c == DLE) { + if (pInfo->last_rx == DLE) { + pInfo->last_rx = 0; + goto char_to_buf; + } + pInfo->last_rx = DLE; + break; + } else if ((c == ETX) && (pInfo->last_rx == DLE)) { + if (pInfo->flags & R3964_BCC) { + pInfo->state = R3964_WAIT_FOR_BCC; + mod_timer(&pInfo->tmr, + jiffies + R3964_TO_ZVZ); + } else { + on_receive_block(pInfo); + } + } else { + pInfo->last_rx = c; +char_to_buf: + pInfo->rx_buf[pInfo->rx_position++] = c; + mod_timer(&pInfo->tmr, jiffies + R3964_TO_ZVZ); + } + } + /* else: overflow-msg? BUF_SIZE>MTU; should not happen? */ + break; + case R3964_WAIT_FOR_BCC: + pInfo->last_rx = c; + on_receive_block(pInfo); + break; + } +} + +static void receive_error(struct r3964_info *pInfo, const char flag) +{ + switch (flag) { + case TTY_NORMAL: + break; + case TTY_BREAK: + TRACE_PE("received break"); + pInfo->flags |= R3964_BREAK; + break; + case TTY_PARITY: + TRACE_PE("parity error"); + pInfo->flags |= R3964_PARITY; + break; + case TTY_FRAME: + TRACE_PE("frame error"); + pInfo->flags |= R3964_FRAME; + break; + case TTY_OVERRUN: + TRACE_PE("frame overrun"); + pInfo->flags |= R3964_OVERRUN; + break; + default: + TRACE_PE("receive_error - unknown flag %d", flag); + pInfo->flags |= R3964_UNKNOWN; + break; + } +} + +static void on_timeout(unsigned long priv) +{ + struct r3964_info *pInfo = (void *)priv; + + switch (pInfo->state) { + case R3964_TX_REQUEST: + TRACE_PE("TX_REQUEST - timeout"); + retry_transmit(pInfo); + break; + case R3964_WAIT_ZVZ_BEFORE_TX_RETRY: + put_char(pInfo, NAK); + flush(pInfo); + retry_transmit(pInfo); + break; + case R3964_WAIT_FOR_TX_ACK: + TRACE_PE("WAIT_FOR_TX_ACK - timeout"); + retry_transmit(pInfo); + break; + case R3964_WAIT_FOR_RX_BUF: + TRACE_PE("WAIT_FOR_RX_BUF - timeout"); + put_char(pInfo, NAK); + flush(pInfo); + pInfo->state = R3964_IDLE; + break; + case R3964_RECEIVING: + TRACE_PE("RECEIVING - timeout after %d chars", + pInfo->rx_position); + put_char(pInfo, NAK); + flush(pInfo); + pInfo->state = R3964_IDLE; + break; + case R3964_WAIT_FOR_RX_REPEAT: + TRACE_PE("WAIT_FOR_RX_REPEAT - timeout"); + pInfo->state = R3964_IDLE; + break; + case R3964_WAIT_FOR_BCC: + TRACE_PE("WAIT_FOR_BCC - timeout"); + put_char(pInfo, NAK); + flush(pInfo); + pInfo->state = R3964_IDLE; + break; + } +} + +static struct r3964_client_info *findClient(struct r3964_info *pInfo, + struct pid *pid) +{ + struct r3964_client_info *pClient; + + for (pClient = pInfo->firstClient; pClient; pClient = pClient->next) { + if (pClient->pid == pid) { + return pClient; + } + } + return NULL; +} + +static int enable_signals(struct r3964_info *pInfo, struct pid *pid, int arg) +{ + struct r3964_client_info *pClient; + struct r3964_client_info **ppClient; + struct r3964_message *pMsg; + + if ((arg & R3964_SIG_ALL) == 0) { + /* Remove client from client list */ + for (ppClient = &pInfo->firstClient; *ppClient; + ppClient = &(*ppClient)->next) { + pClient = *ppClient; + + if (pClient->pid == pid) { + TRACE_PS("removing client %d from client list", + pid_nr(pid)); + *ppClient = pClient->next; + while (pClient->msg_count) { + pMsg = remove_msg(pInfo, pClient); + if (pMsg) { + kfree(pMsg); + TRACE_M("enable_signals - msg " + "kfree %p", pMsg); + } + } + put_pid(pClient->pid); + kfree(pClient); + TRACE_M("enable_signals - kfree %p", pClient); + return 0; + } + } + return -EINVAL; + } else { + pClient = findClient(pInfo, pid); + if (pClient) { + /* update signal options */ + pClient->sig_flags = arg; + } else { + /* add client to client list */ + pClient = kmalloc(sizeof(struct r3964_client_info), + GFP_KERNEL); + TRACE_M("enable_signals - kmalloc %p", pClient); + if (pClient == NULL) + return -ENOMEM; + + TRACE_PS("add client %d to client list", pid_nr(pid)); + spin_lock_init(&pClient->lock); + pClient->sig_flags = arg; + pClient->pid = get_pid(pid); + pClient->next = pInfo->firstClient; + pClient->first_msg = NULL; + pClient->last_msg = NULL; + pClient->next_block_to_read = NULL; + pClient->msg_count = 0; + pInfo->firstClient = pClient; + } + } + + return 0; +} + +static int read_telegram(struct r3964_info *pInfo, struct pid *pid, + unsigned char __user * buf) +{ + struct r3964_client_info *pClient; + struct r3964_block_header *block; + + if (!buf) { + return -EINVAL; + } + + pClient = findClient(pInfo, pid); + if (pClient == NULL) { + return -EINVAL; + } + + block = pClient->next_block_to_read; + if (!block) { + return 0; + } else { + if (copy_to_user(buf, block->data, block->length)) + return -EFAULT; + + remove_client_block(pInfo, pClient); + return block->length; + } + + return -EINVAL; +} + +static void add_msg(struct r3964_client_info *pClient, int msg_id, int arg, + int error_code, struct r3964_block_header *pBlock) +{ + struct r3964_message *pMsg; + unsigned long flags; + + if (pClient->msg_count < R3964_MAX_MSG_COUNT - 1) { +queue_the_message: + + pMsg = kmalloc(sizeof(struct r3964_message), + error_code ? GFP_ATOMIC : GFP_KERNEL); + TRACE_M("add_msg - kmalloc %p", pMsg); + if (pMsg == NULL) { + return; + } + + spin_lock_irqsave(&pClient->lock, flags); + + pMsg->msg_id = msg_id; + pMsg->arg = arg; + pMsg->error_code = error_code; + pMsg->block = pBlock; + pMsg->next = NULL; + + if (pClient->last_msg == NULL) { + pClient->first_msg = pClient->last_msg = pMsg; + } else { + pClient->last_msg->next = pMsg; + pClient->last_msg = pMsg; + } + + pClient->msg_count++; + + if (pBlock != NULL) { + pBlock->locks++; + } + spin_unlock_irqrestore(&pClient->lock, flags); + } else { + if ((pClient->last_msg->msg_id == R3964_MSG_ACK) + && (pClient->last_msg->error_code == R3964_OVERFLOW)) { + pClient->last_msg->arg++; + TRACE_PE("add_msg - inc prev OVERFLOW-msg"); + } else { + msg_id = R3964_MSG_ACK; + arg = 0; + error_code = R3964_OVERFLOW; + pBlock = NULL; + TRACE_PE("add_msg - queue OVERFLOW-msg"); + goto queue_the_message; + } + } + /* Send SIGIO signal to client process: */ + if (pClient->sig_flags & R3964_USE_SIGIO) { + kill_pid(pClient->pid, SIGIO, 1); + } +} + +static struct r3964_message *remove_msg(struct r3964_info *pInfo, + struct r3964_client_info *pClient) +{ + struct r3964_message *pMsg = NULL; + unsigned long flags; + + if (pClient->first_msg) { + spin_lock_irqsave(&pClient->lock, flags); + + pMsg = pClient->first_msg; + pClient->first_msg = pMsg->next; + if (pClient->first_msg == NULL) { + pClient->last_msg = NULL; + } + + pClient->msg_count--; + if (pMsg->block) { + remove_client_block(pInfo, pClient); + pClient->next_block_to_read = pMsg->block; + } + spin_unlock_irqrestore(&pClient->lock, flags); + } + return pMsg; +} + +static void remove_client_block(struct r3964_info *pInfo, + struct r3964_client_info *pClient) +{ + struct r3964_block_header *block; + + TRACE_PS("remove_client_block PID %d", pid_nr(pClient->pid)); + + block = pClient->next_block_to_read; + if (block) { + block->locks--; + if (block->locks == 0) { + remove_from_rx_queue(pInfo, block); + } + } + pClient->next_block_to_read = NULL; +} + +/************************************************************* + * Line discipline routines + *************************************************************/ + +static int r3964_open(struct tty_struct *tty) +{ + struct r3964_info *pInfo; + + TRACE_L("open"); + TRACE_L("tty=%p, PID=%d, disc_data=%p", + tty, current->pid, tty->disc_data); + + pInfo = kmalloc(sizeof(struct r3964_info), GFP_KERNEL); + TRACE_M("r3964_open - info kmalloc %p", pInfo); + + if (!pInfo) { + printk(KERN_ERR "r3964: failed to alloc info structure\n"); + return -ENOMEM; + } + + pInfo->rx_buf = kmalloc(RX_BUF_SIZE, GFP_KERNEL); + TRACE_M("r3964_open - rx_buf kmalloc %p", pInfo->rx_buf); + + if (!pInfo->rx_buf) { + printk(KERN_ERR "r3964: failed to alloc receive buffer\n"); + kfree(pInfo); + TRACE_M("r3964_open - info kfree %p", pInfo); + return -ENOMEM; + } + + pInfo->tx_buf = kmalloc(TX_BUF_SIZE, GFP_KERNEL); + TRACE_M("r3964_open - tx_buf kmalloc %p", pInfo->tx_buf); + + if (!pInfo->tx_buf) { + printk(KERN_ERR "r3964: failed to alloc transmit buffer\n"); + kfree(pInfo->rx_buf); + TRACE_M("r3964_open - rx_buf kfree %p", pInfo->rx_buf); + kfree(pInfo); + TRACE_M("r3964_open - info kfree %p", pInfo); + return -ENOMEM; + } + + spin_lock_init(&pInfo->lock); + pInfo->tty = tty; + init_waitqueue_head(&pInfo->read_wait); + pInfo->priority = R3964_MASTER; + pInfo->rx_first = pInfo->rx_last = NULL; + pInfo->tx_first = pInfo->tx_last = NULL; + pInfo->rx_position = 0; + pInfo->tx_position = 0; + pInfo->last_rx = 0; + pInfo->blocks_in_rx_queue = 0; + pInfo->firstClient = NULL; + pInfo->state = R3964_IDLE; + pInfo->flags = R3964_DEBUG; + pInfo->nRetry = 0; + + tty->disc_data = pInfo; + tty->receive_room = 65536; + + setup_timer(&pInfo->tmr, on_timeout, (unsigned long)pInfo); + + return 0; +} + +static void r3964_close(struct tty_struct *tty) +{ + struct r3964_info *pInfo = tty->disc_data; + struct r3964_client_info *pClient, *pNext; + struct r3964_message *pMsg; + struct r3964_block_header *pHeader, *pNextHeader; + unsigned long flags; + + TRACE_L("close"); + + /* + * Make sure that our task queue isn't activated. If it + * is, take it out of the linked list. + */ + del_timer_sync(&pInfo->tmr); + + /* Remove client-structs and message queues: */ + pClient = pInfo->firstClient; + while (pClient) { + pNext = pClient->next; + while (pClient->msg_count) { + pMsg = remove_msg(pInfo, pClient); + if (pMsg) { + kfree(pMsg); + TRACE_M("r3964_close - msg kfree %p", pMsg); + } + } + put_pid(pClient->pid); + kfree(pClient); + TRACE_M("r3964_close - client kfree %p", pClient); + pClient = pNext; + } + /* Remove jobs from tx_queue: */ + spin_lock_irqsave(&pInfo->lock, flags); + pHeader = pInfo->tx_first; + pInfo->tx_first = pInfo->tx_last = NULL; + spin_unlock_irqrestore(&pInfo->lock, flags); + + while (pHeader) { + pNextHeader = pHeader->next; + kfree(pHeader); + pHeader = pNextHeader; + } + + /* Free buffers: */ + wake_up_interruptible(&pInfo->read_wait); + kfree(pInfo->rx_buf); + TRACE_M("r3964_close - rx_buf kfree %p", pInfo->rx_buf); + kfree(pInfo->tx_buf); + TRACE_M("r3964_close - tx_buf kfree %p", pInfo->tx_buf); + kfree(pInfo); + TRACE_M("r3964_close - info kfree %p", pInfo); +} + +static ssize_t r3964_read(struct tty_struct *tty, struct file *file, + unsigned char __user * buf, size_t nr) +{ + struct r3964_info *pInfo = tty->disc_data; + struct r3964_client_info *pClient; + struct r3964_message *pMsg; + struct r3964_client_message theMsg; + int ret; + + TRACE_L("read()"); + + tty_lock(); + + pClient = findClient(pInfo, task_pid(current)); + if (pClient) { + pMsg = remove_msg(pInfo, pClient); + if (pMsg == NULL) { + /* no messages available. */ + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto unlock; + } + /* block until there is a message: */ + wait_event_interruptible_tty(pInfo->read_wait, + (pMsg = remove_msg(pInfo, pClient))); + } + + /* If we still haven't got a message, we must have been signalled */ + + if (!pMsg) { + ret = -EINTR; + goto unlock; + } + + /* deliver msg to client process: */ + theMsg.msg_id = pMsg->msg_id; + theMsg.arg = pMsg->arg; + theMsg.error_code = pMsg->error_code; + ret = sizeof(struct r3964_client_message); + + kfree(pMsg); + TRACE_M("r3964_read - msg kfree %p", pMsg); + + if (copy_to_user(buf, &theMsg, ret)) { + ret = -EFAULT; + goto unlock; + } + + TRACE_PS("read - return %d", ret); + goto unlock; + } + ret = -EPERM; +unlock: + tty_unlock(); + return ret; +} + +static ssize_t r3964_write(struct tty_struct *tty, struct file *file, + const unsigned char *data, size_t count) +{ + struct r3964_info *pInfo = tty->disc_data; + struct r3964_block_header *pHeader; + struct r3964_client_info *pClient; + unsigned char *new_data; + + TRACE_L("write request, %d characters", count); +/* + * Verify the pointers + */ + + if (!pInfo) + return -EIO; + +/* + * Ensure that the caller does not wish to send too much. + */ + if (count > R3964_MTU) { + if (pInfo->flags & R3964_DEBUG) { + TRACE_L(KERN_WARNING "r3964_write: truncating user " + "packet from %u to mtu %d", count, R3964_MTU); + } + count = R3964_MTU; + } +/* + * Allocate a buffer for the data and copy it from the buffer with header prepended + */ + new_data = kmalloc(count + sizeof(struct r3964_block_header), + GFP_KERNEL); + TRACE_M("r3964_write - kmalloc %p", new_data); + if (new_data == NULL) { + if (pInfo->flags & R3964_DEBUG) { + printk(KERN_ERR "r3964_write: no memory\n"); + } + return -ENOSPC; + } + + pHeader = (struct r3964_block_header *)new_data; + pHeader->data = new_data + sizeof(struct r3964_block_header); + pHeader->length = count; + pHeader->locks = 0; + pHeader->owner = NULL; + + tty_lock(); + + pClient = findClient(pInfo, task_pid(current)); + if (pClient) { + pHeader->owner = pClient; + } + + memcpy(pHeader->data, data, count); /* We already verified this */ + + if (pInfo->flags & R3964_DEBUG) { + dump_block(pHeader->data, count); + } + +/* + * Add buffer to transmit-queue: + */ + add_tx_queue(pInfo, pHeader); + trigger_transmit(pInfo); + + tty_unlock(); + + return 0; +} + +static int r3964_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct r3964_info *pInfo = tty->disc_data; + if (pInfo == NULL) + return -EINVAL; + switch (cmd) { + case R3964_ENABLE_SIGNALS: + return enable_signals(pInfo, task_pid(current), arg); + case R3964_SETPRIORITY: + if (arg < R3964_MASTER || arg > R3964_SLAVE) + return -EINVAL; + pInfo->priority = arg & 0xff; + return 0; + case R3964_USE_BCC: + if (arg) + pInfo->flags |= R3964_BCC; + else + pInfo->flags &= ~R3964_BCC; + return 0; + case R3964_READ_TELEGRAM: + return read_telegram(pInfo, task_pid(current), + (unsigned char __user *)arg); + default: + return -ENOIOCTLCMD; + } +} + +static void r3964_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + TRACE_L("set_termios"); +} + +/* Called without the kernel lock held - fine */ +static unsigned int r3964_poll(struct tty_struct *tty, struct file *file, + struct poll_table_struct *wait) +{ + struct r3964_info *pInfo = tty->disc_data; + struct r3964_client_info *pClient; + struct r3964_message *pMsg = NULL; + unsigned long flags; + int result = POLLOUT; + + TRACE_L("POLL"); + + pClient = findClient(pInfo, task_pid(current)); + if (pClient) { + poll_wait(file, &pInfo->read_wait, wait); + spin_lock_irqsave(&pInfo->lock, flags); + pMsg = pClient->first_msg; + spin_unlock_irqrestore(&pInfo->lock, flags); + if (pMsg) + result |= POLLIN | POLLRDNORM; + } else { + result = -EINVAL; + } + return result; +} + +static void r3964_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct r3964_info *pInfo = tty->disc_data; + const unsigned char *p; + char *f, flags = 0; + int i; + + for (i = count, p = cp, f = fp; i; i--, p++) { + if (f) + flags = *f++; + if (flags == TTY_NORMAL) { + receive_char(pInfo, *p); + } else { + receive_error(pInfo, flags); + } + + } +} + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_R3964); diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c new file mode 100644 index 000000000000..428f4fe0b5f7 --- /dev/null +++ b/drivers/tty/n_tty.c @@ -0,0 +1,2121 @@ +/* + * n_tty.c --- implements the N_TTY line discipline. + * + * This code used to be in tty_io.c, but things are getting hairy + * enough that it made sense to split things off. (The N_TTY + * processing has changed so much that it's hardly recognizable, + * anyway...) + * + * Note that the open routine for N_TTY is guaranteed never to return + * an error. This is because Linux will fall back to setting a line + * to N_TTY if it can not switch to any other line discipline. + * + * Written by Theodore Ts'o, Copyright 1994. + * + * This file also contains code originally written by Linus Torvalds, + * Copyright 1991, 1992, 1993, and by Julian Cowley, Copyright 1994. + * + * This file may be redistributed under the terms of the GNU General Public + * License. + * + * Reduced memory usage for older ARM systems - Russell King. + * + * 2000/01/20 Fixed SMP locking on put_tty_queue using bits of + * the patch by Andrew J. Kroll <ag784@freenet.buffalo.edu> + * who actually finally proved there really was a race. + * + * 2002/03/18 Implemented n_tty_wakeup to send SIGIO POLL_OUTs to + * waiting writing processes-Sapan Bhatia <sapan@corewars.org>. + * Also fixed a bug in BLOCKING mode where n_tty_write returns + * EAGAIN + */ + +#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/timer.h> +#include <linux/ctype.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/bitops.h> +#include <linux/audit.h> +#include <linux/file.h> +#include <linux/uaccess.h> +#include <linux/module.h> + +#include <asm/system.h> + +/* number of characters left in xmit buffer before select has we have room */ +#define WAKEUP_CHARS 256 + +/* + * This defines the low- and high-watermarks for throttling and + * unthrottling the TTY driver. These watermarks are used for + * controlling the space in the read buffer. + */ +#define TTY_THRESHOLD_THROTTLE 128 /* now based on remaining room */ +#define TTY_THRESHOLD_UNTHROTTLE 128 + +/* + * Special byte codes used in the echo buffer to represent operations + * or special handling of characters. Bytes in the echo buffer that + * are not part of such special blocks are treated as normal character + * codes. + */ +#define ECHO_OP_START 0xff +#define ECHO_OP_MOVE_BACK_COL 0x80 +#define ECHO_OP_SET_CANON_COL 0x81 +#define ECHO_OP_ERASE_TAB 0x82 + +static inline int tty_put_user(struct tty_struct *tty, unsigned char x, + unsigned char __user *ptr) +{ + tty_audit_add_data(tty, &x, 1); + return put_user(x, ptr); +} + +/** + * n_tty_set__room - receive space + * @tty: terminal + * + * Called by the driver to find out how much data it is + * permitted to feed to the line discipline without any being lost + * and thus to manage flow control. Not serialized. Answers for the + * "instant". + */ + +static void n_tty_set_room(struct tty_struct *tty) +{ + /* tty->read_cnt is not read locked ? */ + int left = N_TTY_BUF_SIZE - tty->read_cnt - 1; + + /* + * If we are doing input canonicalization, and there are no + * pending newlines, let characters through without limit, so + * that erase characters will be handled. Other excess + * characters will be beeped. + */ + if (left <= 0) + left = tty->icanon && !tty->canon_data; + tty->receive_room = left; +} + +static void put_tty_queue_nolock(unsigned char c, struct tty_struct *tty) +{ + if (tty->read_cnt < N_TTY_BUF_SIZE) { + tty->read_buf[tty->read_head] = c; + tty->read_head = (tty->read_head + 1) & (N_TTY_BUF_SIZE-1); + tty->read_cnt++; + } +} + +/** + * put_tty_queue - add character to tty + * @c: character + * @tty: tty device + * + * Add a character to the tty read_buf queue. This is done under the + * read_lock to serialize character addition and also to protect us + * against parallel reads or flushes + */ + +static void put_tty_queue(unsigned char c, struct tty_struct *tty) +{ + unsigned long flags; + /* + * The problem of stomping on the buffers ends here. + * Why didn't anyone see this one coming? --AJK + */ + spin_lock_irqsave(&tty->read_lock, flags); + put_tty_queue_nolock(c, tty); + spin_unlock_irqrestore(&tty->read_lock, flags); +} + +/** + * check_unthrottle - allow new receive data + * @tty; tty device + * + * Check whether to call the driver unthrottle functions + * + * Can sleep, may be called under the atomic_read_lock mutex but + * this is not guaranteed. + */ +static void check_unthrottle(struct tty_struct *tty) +{ + if (tty->count) + tty_unthrottle(tty); +} + +/** + * reset_buffer_flags - reset buffer state + * @tty: terminal to reset + * + * Reset the read buffer counters, clear the flags, + * and make sure the driver is unthrottled. Called + * from n_tty_open() and n_tty_flush_buffer(). + * + * Locking: tty_read_lock for read fields. + */ + +static void reset_buffer_flags(struct tty_struct *tty) +{ + unsigned long flags; + + spin_lock_irqsave(&tty->read_lock, flags); + tty->read_head = tty->read_tail = tty->read_cnt = 0; + spin_unlock_irqrestore(&tty->read_lock, flags); + + mutex_lock(&tty->echo_lock); + tty->echo_pos = tty->echo_cnt = tty->echo_overrun = 0; + mutex_unlock(&tty->echo_lock); + + tty->canon_head = tty->canon_data = tty->erasing = 0; + memset(&tty->read_flags, 0, sizeof tty->read_flags); + n_tty_set_room(tty); + check_unthrottle(tty); +} + +/** + * n_tty_flush_buffer - clean input queue + * @tty: terminal device + * + * Flush the input buffer. Called when the line discipline is + * being closed, when the tty layer wants the buffer flushed (eg + * at hangup) or when the N_TTY line discipline internally has to + * clean the pending queue (for example some signals). + * + * Locking: ctrl_lock, read_lock. + */ + +static void n_tty_flush_buffer(struct tty_struct *tty) +{ + unsigned long flags; + /* clear everything and unthrottle the driver */ + reset_buffer_flags(tty); + + if (!tty->link) + return; + + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (tty->link->packet) { + tty->ctrl_status |= TIOCPKT_FLUSHREAD; + wake_up_interruptible(&tty->link->read_wait); + } + spin_unlock_irqrestore(&tty->ctrl_lock, flags); +} + +/** + * n_tty_chars_in_buffer - report available bytes + * @tty: tty device + * + * Report the number of characters buffered to be delivered to user + * at this instant in time. + * + * Locking: read_lock + */ + +static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty) +{ + unsigned long flags; + ssize_t n = 0; + + spin_lock_irqsave(&tty->read_lock, flags); + if (!tty->icanon) { + n = tty->read_cnt; + } else if (tty->canon_data) { + n = (tty->canon_head > tty->read_tail) ? + tty->canon_head - tty->read_tail : + tty->canon_head + (N_TTY_BUF_SIZE - tty->read_tail); + } + spin_unlock_irqrestore(&tty->read_lock, flags); + return n; +} + +/** + * is_utf8_continuation - utf8 multibyte check + * @c: byte to check + * + * Returns true if the utf8 character 'c' is a multibyte continuation + * character. We use this to correctly compute the on screen size + * of the character when printing + */ + +static inline int is_utf8_continuation(unsigned char c) +{ + return (c & 0xc0) == 0x80; +} + +/** + * is_continuation - multibyte check + * @c: byte to check + * + * Returns true if the utf8 character 'c' is a multibyte continuation + * character and the terminal is in unicode mode. + */ + +static inline int is_continuation(unsigned char c, struct tty_struct *tty) +{ + return I_IUTF8(tty) && is_utf8_continuation(c); +} + +/** + * do_output_char - output one character + * @c: character (or partial unicode symbol) + * @tty: terminal device + * @space: space available in tty driver write buffer + * + * This is a helper function that handles one output character + * (including special characters like TAB, CR, LF, etc.), + * doing OPOST processing and putting the results in the + * tty driver's write buffer. + * + * Note that Linux currently ignores TABDLY, CRDLY, VTDLY, FFDLY + * and NLDLY. They simply aren't relevant in the world today. + * If you ever need them, add them here. + * + * Returns the number of bytes of buffer space used or -1 if + * no space left. + * + * Locking: should be called under the output_lock to protect + * the column state and space left in the buffer + */ + +static int do_output_char(unsigned char c, struct tty_struct *tty, int space) +{ + int spaces; + + if (!space) + return -1; + + switch (c) { + case '\n': + if (O_ONLRET(tty)) + tty->column = 0; + if (O_ONLCR(tty)) { + if (space < 2) + return -1; + tty->canon_column = tty->column = 0; + tty->ops->write(tty, "\r\n", 2); + return 2; + } + tty->canon_column = tty->column; + break; + case '\r': + if (O_ONOCR(tty) && tty->column == 0) + return 0; + if (O_OCRNL(tty)) { + c = '\n'; + if (O_ONLRET(tty)) + tty->canon_column = tty->column = 0; + break; + } + tty->canon_column = tty->column = 0; + break; + case '\t': + spaces = 8 - (tty->column & 7); + if (O_TABDLY(tty) == XTABS) { + if (space < spaces) + return -1; + tty->column += spaces; + tty->ops->write(tty, " ", spaces); + return spaces; + } + tty->column += spaces; + break; + case '\b': + if (tty->column > 0) + tty->column--; + break; + default: + if (!iscntrl(c)) { + if (O_OLCUC(tty)) + c = toupper(c); + if (!is_continuation(c, tty)) + tty->column++; + } + break; + } + + tty_put_char(tty, c); + return 1; +} + +/** + * process_output - output post processor + * @c: character (or partial unicode symbol) + * @tty: terminal device + * + * Output one character with OPOST processing. + * Returns -1 when the output device is full and the character + * must be retried. + * + * Locking: output_lock to protect column state and space left + * (also, this is called from n_tty_write under the + * tty layer write lock) + */ + +static int process_output(unsigned char c, struct tty_struct *tty) +{ + int space, retval; + + mutex_lock(&tty->output_lock); + + space = tty_write_room(tty); + retval = do_output_char(c, tty, space); + + mutex_unlock(&tty->output_lock); + if (retval < 0) + return -1; + else + return 0; +} + +/** + * process_output_block - block post processor + * @tty: terminal device + * @buf: character buffer + * @nr: number of bytes to output + * + * Output a block of characters with OPOST processing. + * Returns the number of characters output. + * + * This path is used to speed up block console writes, among other + * things when processing blocks of output data. It handles only + * the simple cases normally found and helps to generate blocks of + * symbols for the console driver and thus improve performance. + * + * Locking: output_lock to protect column state and space left + * (also, this is called from n_tty_write under the + * tty layer write lock) + */ + +static ssize_t process_output_block(struct tty_struct *tty, + const unsigned char *buf, unsigned int nr) +{ + int space; + int i; + const unsigned char *cp; + + mutex_lock(&tty->output_lock); + + space = tty_write_room(tty); + if (!space) { + mutex_unlock(&tty->output_lock); + return 0; + } + if (nr > space) + nr = space; + + for (i = 0, cp = buf; i < nr; i++, cp++) { + unsigned char c = *cp; + + switch (c) { + case '\n': + if (O_ONLRET(tty)) + tty->column = 0; + if (O_ONLCR(tty)) + goto break_out; + tty->canon_column = tty->column; + break; + case '\r': + if (O_ONOCR(tty) && tty->column == 0) + goto break_out; + if (O_OCRNL(tty)) + goto break_out; + tty->canon_column = tty->column = 0; + break; + case '\t': + goto break_out; + case '\b': + if (tty->column > 0) + tty->column--; + break; + default: + if (!iscntrl(c)) { + if (O_OLCUC(tty)) + goto break_out; + if (!is_continuation(c, tty)) + tty->column++; + } + break; + } + } +break_out: + i = tty->ops->write(tty, buf, i); + + mutex_unlock(&tty->output_lock); + return i; +} + +/** + * process_echoes - write pending echo characters + * @tty: terminal device + * + * Write previously buffered echo (and other ldisc-generated) + * characters to the tty. + * + * Characters generated by the ldisc (including echoes) need to + * be buffered because the driver's write buffer can fill during + * heavy program output. Echoing straight to the driver will + * often fail under these conditions, causing lost characters and + * resulting mismatches of ldisc state information. + * + * Since the ldisc state must represent the characters actually sent + * to the driver at the time of the write, operations like certain + * changes in column state are also saved in the buffer and executed + * here. + * + * A circular fifo buffer is used so that the most recent characters + * are prioritized. Also, when control characters are echoed with a + * prefixed "^", the pair is treated atomically and thus not separated. + * + * Locking: output_lock to protect column state and space left, + * echo_lock to protect the echo buffer + */ + +static void process_echoes(struct tty_struct *tty) +{ + int space, nr; + unsigned char c; + unsigned char *cp, *buf_end; + + if (!tty->echo_cnt) + return; + + mutex_lock(&tty->output_lock); + mutex_lock(&tty->echo_lock); + + space = tty_write_room(tty); + + buf_end = tty->echo_buf + N_TTY_BUF_SIZE; + cp = tty->echo_buf + tty->echo_pos; + nr = tty->echo_cnt; + while (nr > 0) { + c = *cp; + if (c == ECHO_OP_START) { + unsigned char op; + unsigned char *opp; + int no_space_left = 0; + + /* + * If the buffer byte is the start of a multi-byte + * operation, get the next byte, which is either the + * op code or a control character value. + */ + opp = cp + 1; + if (opp == buf_end) + opp -= N_TTY_BUF_SIZE; + op = *opp; + + switch (op) { + unsigned int num_chars, num_bs; + + case ECHO_OP_ERASE_TAB: + if (++opp == buf_end) + opp -= N_TTY_BUF_SIZE; + num_chars = *opp; + + /* + * Determine how many columns to go back + * in order to erase the tab. + * This depends on the number of columns + * used by other characters within the tab + * area. If this (modulo 8) count is from + * the start of input rather than from a + * previous tab, we offset by canon column. + * Otherwise, tab spacing is normal. + */ + if (!(num_chars & 0x80)) + num_chars += tty->canon_column; + num_bs = 8 - (num_chars & 7); + + if (num_bs > space) { + no_space_left = 1; + break; + } + space -= num_bs; + while (num_bs--) { + tty_put_char(tty, '\b'); + if (tty->column > 0) + tty->column--; + } + cp += 3; + nr -= 3; + break; + + case ECHO_OP_SET_CANON_COL: + tty->canon_column = tty->column; + cp += 2; + nr -= 2; + break; + + case ECHO_OP_MOVE_BACK_COL: + if (tty->column > 0) + tty->column--; + cp += 2; + nr -= 2; + break; + + case ECHO_OP_START: + /* This is an escaped echo op start code */ + if (!space) { + no_space_left = 1; + break; + } + tty_put_char(tty, ECHO_OP_START); + tty->column++; + space--; + cp += 2; + nr -= 2; + break; + + default: + /* + * If the op is not a special byte code, + * it is a ctrl char tagged to be echoed + * as "^X" (where X is the letter + * representing the control char). + * Note that we must ensure there is + * enough space for the whole ctrl pair. + * + */ + if (space < 2) { + no_space_left = 1; + break; + } + tty_put_char(tty, '^'); + tty_put_char(tty, op ^ 0100); + tty->column += 2; + space -= 2; + cp += 2; + nr -= 2; + } + + if (no_space_left) + break; + } else { + if (O_OPOST(tty) && + !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) { + int retval = do_output_char(c, tty, space); + if (retval < 0) + break; + space -= retval; + } else { + if (!space) + break; + tty_put_char(tty, c); + space -= 1; + } + cp += 1; + nr -= 1; + } + + /* When end of circular buffer reached, wrap around */ + if (cp >= buf_end) + cp -= N_TTY_BUF_SIZE; + } + + if (nr == 0) { + tty->echo_pos = 0; + tty->echo_cnt = 0; + tty->echo_overrun = 0; + } else { + int num_processed = tty->echo_cnt - nr; + tty->echo_pos += num_processed; + tty->echo_pos &= N_TTY_BUF_SIZE - 1; + tty->echo_cnt = nr; + if (num_processed > 0) + tty->echo_overrun = 0; + } + + mutex_unlock(&tty->echo_lock); + mutex_unlock(&tty->output_lock); + + if (tty->ops->flush_chars) + tty->ops->flush_chars(tty); +} + +/** + * add_echo_byte - add a byte to the echo buffer + * @c: unicode byte to echo + * @tty: terminal device + * + * Add a character or operation byte to the echo buffer. + * + * Should be called under the echo lock to protect the echo buffer. + */ + +static void add_echo_byte(unsigned char c, struct tty_struct *tty) +{ + int new_byte_pos; + + if (tty->echo_cnt == N_TTY_BUF_SIZE) { + /* Circular buffer is already at capacity */ + new_byte_pos = tty->echo_pos; + + /* + * Since the buffer start position needs to be advanced, + * be sure to step by a whole operation byte group. + */ + if (tty->echo_buf[tty->echo_pos] == ECHO_OP_START) { + if (tty->echo_buf[(tty->echo_pos + 1) & + (N_TTY_BUF_SIZE - 1)] == + ECHO_OP_ERASE_TAB) { + tty->echo_pos += 3; + tty->echo_cnt -= 2; + } else { + tty->echo_pos += 2; + tty->echo_cnt -= 1; + } + } else { + tty->echo_pos++; + } + tty->echo_pos &= N_TTY_BUF_SIZE - 1; + + tty->echo_overrun = 1; + } else { + new_byte_pos = tty->echo_pos + tty->echo_cnt; + new_byte_pos &= N_TTY_BUF_SIZE - 1; + tty->echo_cnt++; + } + + tty->echo_buf[new_byte_pos] = c; +} + +/** + * echo_move_back_col - add operation to move back a column + * @tty: terminal device + * + * Add an operation to the echo buffer to move back one column. + * + * Locking: echo_lock to protect the echo buffer + */ + +static void echo_move_back_col(struct tty_struct *tty) +{ + mutex_lock(&tty->echo_lock); + + add_echo_byte(ECHO_OP_START, tty); + add_echo_byte(ECHO_OP_MOVE_BACK_COL, tty); + + mutex_unlock(&tty->echo_lock); +} + +/** + * echo_set_canon_col - add operation to set the canon column + * @tty: terminal device + * + * Add an operation to the echo buffer to set the canon column + * to the current column. + * + * Locking: echo_lock to protect the echo buffer + */ + +static void echo_set_canon_col(struct tty_struct *tty) +{ + mutex_lock(&tty->echo_lock); + + add_echo_byte(ECHO_OP_START, tty); + add_echo_byte(ECHO_OP_SET_CANON_COL, tty); + + mutex_unlock(&tty->echo_lock); +} + +/** + * echo_erase_tab - add operation to erase a tab + * @num_chars: number of character columns already used + * @after_tab: true if num_chars starts after a previous tab + * @tty: terminal device + * + * Add an operation to the echo buffer to erase a tab. + * + * Called by the eraser function, which knows how many character + * columns have been used since either a previous tab or the start + * of input. This information will be used later, along with + * canon column (if applicable), to go back the correct number + * of columns. + * + * Locking: echo_lock to protect the echo buffer + */ + +static void echo_erase_tab(unsigned int num_chars, int after_tab, + struct tty_struct *tty) +{ + mutex_lock(&tty->echo_lock); + + add_echo_byte(ECHO_OP_START, tty); + add_echo_byte(ECHO_OP_ERASE_TAB, tty); + + /* We only need to know this modulo 8 (tab spacing) */ + num_chars &= 7; + + /* Set the high bit as a flag if num_chars is after a previous tab */ + if (after_tab) + num_chars |= 0x80; + + add_echo_byte(num_chars, tty); + + mutex_unlock(&tty->echo_lock); +} + +/** + * echo_char_raw - echo a character raw + * @c: unicode byte to echo + * @tty: terminal device + * + * Echo user input back onto the screen. This must be called only when + * L_ECHO(tty) is true. Called from the driver receive_buf path. + * + * This variant does not treat control characters specially. + * + * Locking: echo_lock to protect the echo buffer + */ + +static void echo_char_raw(unsigned char c, struct tty_struct *tty) +{ + mutex_lock(&tty->echo_lock); + + if (c == ECHO_OP_START) { + add_echo_byte(ECHO_OP_START, tty); + add_echo_byte(ECHO_OP_START, tty); + } else { + add_echo_byte(c, tty); + } + + mutex_unlock(&tty->echo_lock); +} + +/** + * echo_char - echo a character + * @c: unicode byte to echo + * @tty: terminal device + * + * Echo user input back onto the screen. This must be called only when + * L_ECHO(tty) is true. Called from the driver receive_buf path. + * + * This variant tags control characters to be echoed as "^X" + * (where X is the letter representing the control char). + * + * Locking: echo_lock to protect the echo buffer + */ + +static void echo_char(unsigned char c, struct tty_struct *tty) +{ + mutex_lock(&tty->echo_lock); + + if (c == ECHO_OP_START) { + add_echo_byte(ECHO_OP_START, tty); + add_echo_byte(ECHO_OP_START, tty); + } else { + if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t') + add_echo_byte(ECHO_OP_START, tty); + add_echo_byte(c, tty); + } + + mutex_unlock(&tty->echo_lock); +} + +/** + * finish_erasing - complete erase + * @tty: tty doing the erase + */ + +static inline void finish_erasing(struct tty_struct *tty) +{ + if (tty->erasing) { + echo_char_raw('/', tty); + tty->erasing = 0; + } +} + +/** + * eraser - handle erase function + * @c: character input + * @tty: terminal device + * + * Perform erase and necessary output when an erase character is + * present in the stream from the driver layer. Handles the complexities + * of UTF-8 multibyte symbols. + * + * Locking: read_lock for tty buffers + */ + +static void eraser(unsigned char c, struct tty_struct *tty) +{ + enum { ERASE, WERASE, KILL } kill_type; + int head, seen_alnums, cnt; + unsigned long flags; + + /* FIXME: locking needed ? */ + if (tty->read_head == tty->canon_head) { + /* process_output('\a', tty); */ /* what do you think? */ + return; + } + if (c == ERASE_CHAR(tty)) + kill_type = ERASE; + else if (c == WERASE_CHAR(tty)) + kill_type = WERASE; + else { + if (!L_ECHO(tty)) { + spin_lock_irqsave(&tty->read_lock, flags); + tty->read_cnt -= ((tty->read_head - tty->canon_head) & + (N_TTY_BUF_SIZE - 1)); + tty->read_head = tty->canon_head; + spin_unlock_irqrestore(&tty->read_lock, flags); + return; + } + if (!L_ECHOK(tty) || !L_ECHOKE(tty) || !L_ECHOE(tty)) { + spin_lock_irqsave(&tty->read_lock, flags); + tty->read_cnt -= ((tty->read_head - tty->canon_head) & + (N_TTY_BUF_SIZE - 1)); + tty->read_head = tty->canon_head; + spin_unlock_irqrestore(&tty->read_lock, flags); + finish_erasing(tty); + echo_char(KILL_CHAR(tty), tty); + /* Add a newline if ECHOK is on and ECHOKE is off. */ + if (L_ECHOK(tty)) + echo_char_raw('\n', tty); + return; + } + kill_type = KILL; + } + + seen_alnums = 0; + /* FIXME: Locking ?? */ + while (tty->read_head != tty->canon_head) { + head = tty->read_head; + + /* erase a single possibly multibyte character */ + do { + head = (head - 1) & (N_TTY_BUF_SIZE-1); + c = tty->read_buf[head]; + } while (is_continuation(c, tty) && head != tty->canon_head); + + /* do not partially erase */ + if (is_continuation(c, tty)) + break; + + if (kill_type == WERASE) { + /* Equivalent to BSD's ALTWERASE. */ + if (isalnum(c) || c == '_') + seen_alnums++; + else if (seen_alnums) + break; + } + cnt = (tty->read_head - head) & (N_TTY_BUF_SIZE-1); + spin_lock_irqsave(&tty->read_lock, flags); + tty->read_head = head; + tty->read_cnt -= cnt; + spin_unlock_irqrestore(&tty->read_lock, flags); + if (L_ECHO(tty)) { + if (L_ECHOPRT(tty)) { + if (!tty->erasing) { + echo_char_raw('\\', tty); + tty->erasing = 1; + } + /* if cnt > 1, output a multi-byte character */ + echo_char(c, tty); + while (--cnt > 0) { + head = (head+1) & (N_TTY_BUF_SIZE-1); + echo_char_raw(tty->read_buf[head], tty); + echo_move_back_col(tty); + } + } else if (kill_type == ERASE && !L_ECHOE(tty)) { + echo_char(ERASE_CHAR(tty), tty); + } else if (c == '\t') { + unsigned int num_chars = 0; + int after_tab = 0; + unsigned long tail = tty->read_head; + + /* + * Count the columns used for characters + * since the start of input or after a + * previous tab. + * This info is used to go back the correct + * number of columns. + */ + while (tail != tty->canon_head) { + tail = (tail-1) & (N_TTY_BUF_SIZE-1); + c = tty->read_buf[tail]; + if (c == '\t') { + after_tab = 1; + break; + } else if (iscntrl(c)) { + if (L_ECHOCTL(tty)) + num_chars += 2; + } else if (!is_continuation(c, tty)) { + num_chars++; + } + } + echo_erase_tab(num_chars, after_tab, tty); + } else { + if (iscntrl(c) && L_ECHOCTL(tty)) { + echo_char_raw('\b', tty); + echo_char_raw(' ', tty); + echo_char_raw('\b', tty); + } + if (!iscntrl(c) || L_ECHOCTL(tty)) { + echo_char_raw('\b', tty); + echo_char_raw(' ', tty); + echo_char_raw('\b', tty); + } + } + } + if (kill_type == ERASE) + break; + } + if (tty->read_head == tty->canon_head && L_ECHO(tty)) + finish_erasing(tty); +} + +/** + * isig - handle the ISIG optio + * @sig: signal + * @tty: terminal + * @flush: force flush + * + * Called when a signal is being sent due to terminal input. This + * may caus terminal flushing to take place according to the termios + * settings and character used. Called from the driver receive_buf + * path so serialized. + * + * Locking: ctrl_lock, read_lock (both via flush buffer) + */ + +static inline void isig(int sig, struct tty_struct *tty, int flush) +{ + if (tty->pgrp) + kill_pgrp(tty->pgrp, sig, 1); + if (flush || !L_NOFLSH(tty)) { + n_tty_flush_buffer(tty); + tty_driver_flush_buffer(tty); + } +} + +/** + * n_tty_receive_break - handle break + * @tty: terminal + * + * An RS232 break event has been hit in the incoming bitstream. This + * can cause a variety of events depending upon the termios settings. + * + * Called from the receive_buf path so single threaded. + */ + +static inline void n_tty_receive_break(struct tty_struct *tty) +{ + if (I_IGNBRK(tty)) + return; + if (I_BRKINT(tty)) { + isig(SIGINT, tty, 1); + return; + } + if (I_PARMRK(tty)) { + put_tty_queue('\377', tty); + put_tty_queue('\0', tty); + } + put_tty_queue('\0', tty); + wake_up_interruptible(&tty->read_wait); +} + +/** + * n_tty_receive_overrun - handle overrun reporting + * @tty: terminal + * + * Data arrived faster than we could process it. While the tty + * driver has flagged this the bits that were missed are gone + * forever. + * + * Called from the receive_buf path so single threaded. Does not + * need locking as num_overrun and overrun_time are function + * private. + */ + +static inline void n_tty_receive_overrun(struct tty_struct *tty) +{ + char buf[64]; + + tty->num_overrun++; + if (time_before(tty->overrun_time, jiffies - HZ) || + time_after(tty->overrun_time, jiffies)) { + printk(KERN_WARNING "%s: %d input overrun(s)\n", + tty_name(tty, buf), + tty->num_overrun); + tty->overrun_time = jiffies; + tty->num_overrun = 0; + } +} + +/** + * n_tty_receive_parity_error - error notifier + * @tty: terminal device + * @c: character + * + * Process a parity error and queue the right data to indicate + * the error case if necessary. Locking as per n_tty_receive_buf. + */ +static inline void n_tty_receive_parity_error(struct tty_struct *tty, + unsigned char c) +{ + if (I_IGNPAR(tty)) + return; + if (I_PARMRK(tty)) { + put_tty_queue('\377', tty); + put_tty_queue('\0', tty); + put_tty_queue(c, tty); + } else if (I_INPCK(tty)) + put_tty_queue('\0', tty); + else + put_tty_queue(c, tty); + wake_up_interruptible(&tty->read_wait); +} + +/** + * n_tty_receive_char - perform processing + * @tty: terminal device + * @c: character + * + * Process an individual character of input received from the driver. + * This is serialized with respect to itself by the rules for the + * driver above. + */ + +static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c) +{ + unsigned long flags; + int parmrk; + + if (tty->raw) { + put_tty_queue(c, tty); + return; + } + + if (I_ISTRIP(tty)) + c &= 0x7f; + if (I_IUCLC(tty) && L_IEXTEN(tty)) + c = tolower(c); + + if (L_EXTPROC(tty)) { + put_tty_queue(c, tty); + return; + } + + if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && + I_IXANY(tty) && c != START_CHAR(tty) && c != STOP_CHAR(tty) && + c != INTR_CHAR(tty) && c != QUIT_CHAR(tty) && c != SUSP_CHAR(tty)) { + start_tty(tty); + process_echoes(tty); + } + + if (tty->closing) { + if (I_IXON(tty)) { + if (c == START_CHAR(tty)) { + start_tty(tty); + process_echoes(tty); + } else if (c == STOP_CHAR(tty)) + stop_tty(tty); + } + return; + } + + /* + * If the previous character was LNEXT, or we know that this + * character is not one of the characters that we'll have to + * handle specially, do shortcut processing to speed things + * up. + */ + if (!test_bit(c, tty->process_char_map) || tty->lnext) { + tty->lnext = 0; + parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0; + if (tty->read_cnt >= (N_TTY_BUF_SIZE - parmrk - 1)) { + /* beep if no space */ + if (L_ECHO(tty)) + process_output('\a', tty); + return; + } + if (L_ECHO(tty)) { + finish_erasing(tty); + /* Record the column of first canon char. */ + if (tty->canon_head == tty->read_head) + echo_set_canon_col(tty); + echo_char(c, tty); + process_echoes(tty); + } + if (parmrk) + put_tty_queue(c, tty); + put_tty_queue(c, tty); + return; + } + + if (I_IXON(tty)) { + if (c == START_CHAR(tty)) { + start_tty(tty); + process_echoes(tty); + return; + } + if (c == STOP_CHAR(tty)) { + stop_tty(tty); + return; + } + } + + if (L_ISIG(tty)) { + int signal; + signal = SIGINT; + if (c == INTR_CHAR(tty)) + goto send_signal; + signal = SIGQUIT; + if (c == QUIT_CHAR(tty)) + goto send_signal; + signal = SIGTSTP; + if (c == SUSP_CHAR(tty)) { +send_signal: + /* + * Note that we do not use isig() here because we want + * the order to be: + * 1) flush, 2) echo, 3) signal + */ + if (!L_NOFLSH(tty)) { + n_tty_flush_buffer(tty); + tty_driver_flush_buffer(tty); + } + if (I_IXON(tty)) + start_tty(tty); + if (L_ECHO(tty)) { + echo_char(c, tty); + process_echoes(tty); + } + if (tty->pgrp) + kill_pgrp(tty->pgrp, signal, 1); + return; + } + } + + if (c == '\r') { + if (I_IGNCR(tty)) + return; + if (I_ICRNL(tty)) + c = '\n'; + } else if (c == '\n' && I_INLCR(tty)) + c = '\r'; + + if (tty->icanon) { + if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) || + (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) { + eraser(c, tty); + process_echoes(tty); + return; + } + if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) { + tty->lnext = 1; + if (L_ECHO(tty)) { + finish_erasing(tty); + if (L_ECHOCTL(tty)) { + echo_char_raw('^', tty); + echo_char_raw('\b', tty); + process_echoes(tty); + } + } + return; + } + if (c == REPRINT_CHAR(tty) && L_ECHO(tty) && + L_IEXTEN(tty)) { + unsigned long tail = tty->canon_head; + + finish_erasing(tty); + echo_char(c, tty); + echo_char_raw('\n', tty); + while (tail != tty->read_head) { + echo_char(tty->read_buf[tail], tty); + tail = (tail+1) & (N_TTY_BUF_SIZE-1); + } + process_echoes(tty); + return; + } + if (c == '\n') { + if (tty->read_cnt >= N_TTY_BUF_SIZE) { + if (L_ECHO(tty)) + process_output('\a', tty); + return; + } + if (L_ECHO(tty) || L_ECHONL(tty)) { + echo_char_raw('\n', tty); + process_echoes(tty); + } + goto handle_newline; + } + if (c == EOF_CHAR(tty)) { + if (tty->read_cnt >= N_TTY_BUF_SIZE) + return; + if (tty->canon_head != tty->read_head) + set_bit(TTY_PUSH, &tty->flags); + c = __DISABLED_CHAR; + goto handle_newline; + } + if ((c == EOL_CHAR(tty)) || + (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) { + parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) + ? 1 : 0; + if (tty->read_cnt >= (N_TTY_BUF_SIZE - parmrk)) { + if (L_ECHO(tty)) + process_output('\a', tty); + return; + } + /* + * XXX are EOL_CHAR and EOL2_CHAR echoed?!? + */ + if (L_ECHO(tty)) { + /* Record the column of first canon char. */ + if (tty->canon_head == tty->read_head) + echo_set_canon_col(tty); + echo_char(c, tty); + process_echoes(tty); + } + /* + * XXX does PARMRK doubling happen for + * EOL_CHAR and EOL2_CHAR? + */ + if (parmrk) + put_tty_queue(c, tty); + +handle_newline: + spin_lock_irqsave(&tty->read_lock, flags); + set_bit(tty->read_head, tty->read_flags); + put_tty_queue_nolock(c, tty); + tty->canon_head = tty->read_head; + tty->canon_data++; + spin_unlock_irqrestore(&tty->read_lock, flags); + kill_fasync(&tty->fasync, SIGIO, POLL_IN); + if (waitqueue_active(&tty->read_wait)) + wake_up_interruptible(&tty->read_wait); + return; + } + } + + parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0; + if (tty->read_cnt >= (N_TTY_BUF_SIZE - parmrk - 1)) { + /* beep if no space */ + if (L_ECHO(tty)) + process_output('\a', tty); + return; + } + if (L_ECHO(tty)) { + finish_erasing(tty); + if (c == '\n') + echo_char_raw('\n', tty); + else { + /* Record the column of first canon char. */ + if (tty->canon_head == tty->read_head) + echo_set_canon_col(tty); + echo_char(c, tty); + } + process_echoes(tty); + } + + if (parmrk) + put_tty_queue(c, tty); + + put_tty_queue(c, tty); +} + + +/** + * n_tty_write_wakeup - asynchronous I/O notifier + * @tty: tty device + * + * Required for the ptys, serial driver etc. since processes + * that attach themselves to the master and rely on ASYNC + * IO must be woken up + */ + +static void n_tty_write_wakeup(struct tty_struct *tty) +{ + if (tty->fasync && test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) + kill_fasync(&tty->fasync, SIGIO, POLL_OUT); +} + +/** + * n_tty_receive_buf - data receive + * @tty: terminal device + * @cp: buffer + * @fp: flag buffer + * @count: characters + * + * Called by the terminal driver when a block of characters has + * been received. This function must be called from soft contexts + * not from interrupt context. The driver is responsible for making + * calls one at a time and in order (or using flush_to_ldisc) + */ + +static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + const unsigned char *p; + char *f, flags = TTY_NORMAL; + int i; + char buf[64]; + unsigned long cpuflags; + + if (!tty->read_buf) + return; + + if (tty->real_raw) { + spin_lock_irqsave(&tty->read_lock, cpuflags); + i = min(N_TTY_BUF_SIZE - tty->read_cnt, + N_TTY_BUF_SIZE - tty->read_head); + i = min(count, i); + memcpy(tty->read_buf + tty->read_head, cp, i); + tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1); + tty->read_cnt += i; + cp += i; + count -= i; + + i = min(N_TTY_BUF_SIZE - tty->read_cnt, + N_TTY_BUF_SIZE - tty->read_head); + i = min(count, i); + memcpy(tty->read_buf + tty->read_head, cp, i); + tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1); + tty->read_cnt += i; + spin_unlock_irqrestore(&tty->read_lock, cpuflags); + } else { + for (i = count, p = cp, f = fp; i; i--, p++) { + if (f) + flags = *f++; + switch (flags) { + case TTY_NORMAL: + n_tty_receive_char(tty, *p); + break; + case TTY_BREAK: + n_tty_receive_break(tty); + break; + case TTY_PARITY: + case TTY_FRAME: + n_tty_receive_parity_error(tty, *p); + break; + case TTY_OVERRUN: + n_tty_receive_overrun(tty); + break; + default: + printk(KERN_ERR "%s: unknown flag %d\n", + tty_name(tty, buf), flags); + break; + } + } + if (tty->ops->flush_chars) + tty->ops->flush_chars(tty); + } + + n_tty_set_room(tty); + + if ((!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) || + L_EXTPROC(tty)) { + kill_fasync(&tty->fasync, SIGIO, POLL_IN); + if (waitqueue_active(&tty->read_wait)) + wake_up_interruptible(&tty->read_wait); + } + + /* + * Check the remaining room for the input canonicalization + * mode. We don't want to throttle the driver if we're in + * canonical mode and don't have a newline yet! + */ + if (tty->receive_room < TTY_THRESHOLD_THROTTLE) + tty_throttle(tty); +} + +int is_ignored(int sig) +{ + return (sigismember(¤t->blocked, sig) || + current->sighand->action[sig-1].sa.sa_handler == SIG_IGN); +} + +/** + * n_tty_set_termios - termios data changed + * @tty: terminal + * @old: previous data + * + * Called by the tty layer when the user changes termios flags so + * that the line discipline can plan ahead. This function cannot sleep + * and is protected from re-entry by the tty layer. The user is + * guaranteed that this function will not be re-entered or in progress + * when the ldisc is closed. + * + * Locking: Caller holds tty->termios_mutex + */ + +static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + int canon_change = 1; + BUG_ON(!tty); + + if (old) + canon_change = (old->c_lflag ^ tty->termios->c_lflag) & ICANON; + if (canon_change) { + memset(&tty->read_flags, 0, sizeof tty->read_flags); + tty->canon_head = tty->read_tail; + tty->canon_data = 0; + tty->erasing = 0; + } + + if (canon_change && !L_ICANON(tty) && tty->read_cnt) + wake_up_interruptible(&tty->read_wait); + + tty->icanon = (L_ICANON(tty) != 0); + if (test_bit(TTY_HW_COOK_IN, &tty->flags)) { + tty->raw = 1; + tty->real_raw = 1; + n_tty_set_room(tty); + return; + } + if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) || + I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) || + I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) || + I_PARMRK(tty)) { + memset(tty->process_char_map, 0, 256/8); + + if (I_IGNCR(tty) || I_ICRNL(tty)) + set_bit('\r', tty->process_char_map); + if (I_INLCR(tty)) + set_bit('\n', tty->process_char_map); + + if (L_ICANON(tty)) { + set_bit(ERASE_CHAR(tty), tty->process_char_map); + set_bit(KILL_CHAR(tty), tty->process_char_map); + set_bit(EOF_CHAR(tty), tty->process_char_map); + set_bit('\n', tty->process_char_map); + set_bit(EOL_CHAR(tty), tty->process_char_map); + if (L_IEXTEN(tty)) { + set_bit(WERASE_CHAR(tty), + tty->process_char_map); + set_bit(LNEXT_CHAR(tty), + tty->process_char_map); + set_bit(EOL2_CHAR(tty), + tty->process_char_map); + if (L_ECHO(tty)) + set_bit(REPRINT_CHAR(tty), + tty->process_char_map); + } + } + if (I_IXON(tty)) { + set_bit(START_CHAR(tty), tty->process_char_map); + set_bit(STOP_CHAR(tty), tty->process_char_map); + } + if (L_ISIG(tty)) { + set_bit(INTR_CHAR(tty), tty->process_char_map); + set_bit(QUIT_CHAR(tty), tty->process_char_map); + set_bit(SUSP_CHAR(tty), tty->process_char_map); + } + clear_bit(__DISABLED_CHAR, tty->process_char_map); + tty->raw = 0; + tty->real_raw = 0; + } else { + tty->raw = 1; + if ((I_IGNBRK(tty) || (!I_BRKINT(tty) && !I_PARMRK(tty))) && + (I_IGNPAR(tty) || !I_INPCK(tty)) && + (tty->driver->flags & TTY_DRIVER_REAL_RAW)) + tty->real_raw = 1; + else + tty->real_raw = 0; + } + n_tty_set_room(tty); + /* The termios change make the tty ready for I/O */ + wake_up_interruptible(&tty->write_wait); + wake_up_interruptible(&tty->read_wait); +} + +/** + * n_tty_close - close the ldisc for this tty + * @tty: device + * + * Called from the terminal layer when this line discipline is + * being shut down, either because of a close or becsuse of a + * discipline change. The function will not be called while other + * ldisc methods are in progress. + */ + +static void n_tty_close(struct tty_struct *tty) +{ + n_tty_flush_buffer(tty); + if (tty->read_buf) { + kfree(tty->read_buf); + tty->read_buf = NULL; + } + if (tty->echo_buf) { + kfree(tty->echo_buf); + tty->echo_buf = NULL; + } +} + +/** + * n_tty_open - open an ldisc + * @tty: terminal to open + * + * Called when this line discipline is being attached to the + * terminal device. Can sleep. Called serialized so that no + * other events will occur in parallel. No further open will occur + * until a close. + */ + +static int n_tty_open(struct tty_struct *tty) +{ + if (!tty) + return -EINVAL; + + /* These are ugly. Currently a malloc failure here can panic */ + if (!tty->read_buf) { + tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL); + if (!tty->read_buf) + return -ENOMEM; + } + if (!tty->echo_buf) { + tty->echo_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL); + + if (!tty->echo_buf) + return -ENOMEM; + } + reset_buffer_flags(tty); + tty->column = 0; + n_tty_set_termios(tty, NULL); + tty->minimum_to_wake = 1; + tty->closing = 0; + return 0; +} + +static inline int input_available_p(struct tty_struct *tty, int amt) +{ + tty_flush_to_ldisc(tty); + if (tty->icanon && !L_EXTPROC(tty)) { + if (tty->canon_data) + return 1; + } else if (tty->read_cnt >= (amt ? amt : 1)) + return 1; + + return 0; +} + +/** + * copy_from_read_buf - copy read data directly + * @tty: terminal device + * @b: user data + * @nr: size of data + * + * Helper function to speed up n_tty_read. It is only called when + * ICANON is off; it copies characters straight from the tty queue to + * user space directly. It can be profitably called twice; once to + * drain the space from the tail pointer to the (physical) end of the + * buffer, and once to drain the space from the (physical) beginning of + * the buffer to head pointer. + * + * Called under the tty->atomic_read_lock sem + * + */ + +static int copy_from_read_buf(struct tty_struct *tty, + unsigned char __user **b, + size_t *nr) + +{ + int retval; + size_t n; + unsigned long flags; + + retval = 0; + spin_lock_irqsave(&tty->read_lock, flags); + n = min(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail); + n = min(*nr, n); + spin_unlock_irqrestore(&tty->read_lock, flags); + if (n) { + retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n); + n -= retval; + tty_audit_add_data(tty, &tty->read_buf[tty->read_tail], n); + spin_lock_irqsave(&tty->read_lock, flags); + tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1); + tty->read_cnt -= n; + /* Turn single EOF into zero-length read */ + if (L_EXTPROC(tty) && tty->icanon && n == 1) { + if (!tty->read_cnt && (*b)[n-1] == EOF_CHAR(tty)) + n--; + } + spin_unlock_irqrestore(&tty->read_lock, flags); + *b += n; + *nr -= n; + } + return retval; +} + +extern ssize_t redirected_tty_write(struct file *, const char __user *, + size_t, loff_t *); + +/** + * job_control - check job control + * @tty: tty + * @file: file handle + * + * Perform job control management checks on this file/tty descriptor + * and if appropriate send any needed signals and return a negative + * error code if action should be taken. + * + * FIXME: + * Locking: None - redirected write test is safe, testing + * current->signal should possibly lock current->sighand + * pgrp locking ? + */ + +static int job_control(struct tty_struct *tty, struct file *file) +{ + /* Job control check -- must be done at start and after + every sleep (POSIX.1 7.1.1.4). */ + /* NOTE: not yet done after every sleep pending a thorough + check of the logic of this change. -- jlc */ + /* don't stop on /dev/console */ + if (file->f_op->write != redirected_tty_write && + current->signal->tty == tty) { + if (!tty->pgrp) + printk(KERN_ERR "n_tty_read: no tty->pgrp!\n"); + else if (task_pgrp(current) != tty->pgrp) { + if (is_ignored(SIGTTIN) || + is_current_pgrp_orphaned()) + return -EIO; + kill_pgrp(task_pgrp(current), SIGTTIN, 1); + set_thread_flag(TIF_SIGPENDING); + return -ERESTARTSYS; + } + } + return 0; +} + + +/** + * n_tty_read - read function for tty + * @tty: tty device + * @file: file object + * @buf: userspace buffer pointer + * @nr: size of I/O + * + * Perform reads for the line discipline. We are guaranteed that the + * line discipline will not be closed under us but we may get multiple + * parallel readers and must handle this ourselves. We may also get + * a hangup. Always called in user context, may sleep. + * + * This code must be sure never to sleep through a hangup. + */ + +static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, + unsigned char __user *buf, size_t nr) +{ + unsigned char __user *b = buf; + DECLARE_WAITQUEUE(wait, current); + int c; + int minimum, time; + ssize_t retval = 0; + ssize_t size; + long timeout; + unsigned long flags; + int packet; + +do_it_again: + + BUG_ON(!tty->read_buf); + + c = job_control(tty, file); + if (c < 0) + return c; + + minimum = time = 0; + timeout = MAX_SCHEDULE_TIMEOUT; + if (!tty->icanon) { + time = (HZ / 10) * TIME_CHAR(tty); + minimum = MIN_CHAR(tty); + if (minimum) { + if (time) + tty->minimum_to_wake = 1; + else if (!waitqueue_active(&tty->read_wait) || + (tty->minimum_to_wake > minimum)) + tty->minimum_to_wake = minimum; + } else { + timeout = 0; + if (time) { + timeout = time; + time = 0; + } + tty->minimum_to_wake = minimum = 1; + } + } + + /* + * Internal serialization of reads. + */ + if (file->f_flags & O_NONBLOCK) { + if (!mutex_trylock(&tty->atomic_read_lock)) + return -EAGAIN; + } else { + if (mutex_lock_interruptible(&tty->atomic_read_lock)) + return -ERESTARTSYS; + } + packet = tty->packet; + + add_wait_queue(&tty->read_wait, &wait); + while (nr) { + /* First test for status change. */ + if (packet && tty->link->ctrl_status) { + unsigned char cs; + if (b != buf) + break; + spin_lock_irqsave(&tty->link->ctrl_lock, flags); + cs = tty->link->ctrl_status; + tty->link->ctrl_status = 0; + spin_unlock_irqrestore(&tty->link->ctrl_lock, flags); + if (tty_put_user(tty, cs, b++)) { + retval = -EFAULT; + b--; + break; + } + nr--; + break; + } + /* This statement must be first before checking for input + so that any interrupt will set the state back to + TASK_RUNNING. */ + set_current_state(TASK_INTERRUPTIBLE); + + if (((minimum - (b - buf)) < tty->minimum_to_wake) && + ((minimum - (b - buf)) >= 1)) + tty->minimum_to_wake = (minimum - (b - buf)); + + if (!input_available_p(tty, 0)) { + if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) { + retval = -EIO; + break; + } + if (tty_hung_up_p(file)) + break; + if (!timeout) + break; + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + /* FIXME: does n_tty_set_room need locking ? */ + n_tty_set_room(tty); + timeout = schedule_timeout(timeout); + continue; + } + __set_current_state(TASK_RUNNING); + + /* Deal with packet mode. */ + if (packet && b == buf) { + if (tty_put_user(tty, TIOCPKT_DATA, b++)) { + retval = -EFAULT; + b--; + break; + } + nr--; + } + + if (tty->icanon && !L_EXTPROC(tty)) { + /* N.B. avoid overrun if nr == 0 */ + while (nr && tty->read_cnt) { + int eol; + + eol = test_and_clear_bit(tty->read_tail, + tty->read_flags); + c = tty->read_buf[tty->read_tail]; + spin_lock_irqsave(&tty->read_lock, flags); + tty->read_tail = ((tty->read_tail+1) & + (N_TTY_BUF_SIZE-1)); + tty->read_cnt--; + if (eol) { + /* this test should be redundant: + * we shouldn't be reading data if + * canon_data is 0 + */ + if (--tty->canon_data < 0) + tty->canon_data = 0; + } + spin_unlock_irqrestore(&tty->read_lock, flags); + + if (!eol || (c != __DISABLED_CHAR)) { + if (tty_put_user(tty, c, b++)) { + retval = -EFAULT; + b--; + break; + } + nr--; + } + if (eol) { + tty_audit_push(tty); + break; + } + } + if (retval) + break; + } else { + int uncopied; + /* The copy function takes the read lock and handles + locking internally for this case */ + uncopied = copy_from_read_buf(tty, &b, &nr); + uncopied += copy_from_read_buf(tty, &b, &nr); + if (uncopied) { + retval = -EFAULT; + break; + } + } + + /* If there is enough space in the read buffer now, let the + * low-level driver know. We use n_tty_chars_in_buffer() to + * check the buffer, as it now knows about canonical mode. + * Otherwise, if the driver is throttled and the line is + * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode, + * we won't get any more characters. + */ + if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) { + n_tty_set_room(tty); + check_unthrottle(tty); + } + + if (b - buf >= minimum) + break; + if (time) + timeout = time; + } + mutex_unlock(&tty->atomic_read_lock); + remove_wait_queue(&tty->read_wait, &wait); + + if (!waitqueue_active(&tty->read_wait)) + tty->minimum_to_wake = minimum; + + __set_current_state(TASK_RUNNING); + size = b - buf; + if (size) { + retval = size; + if (nr) + clear_bit(TTY_PUSH, &tty->flags); + } else if (test_and_clear_bit(TTY_PUSH, &tty->flags)) + goto do_it_again; + + n_tty_set_room(tty); + return retval; +} + +/** + * n_tty_write - write function for tty + * @tty: tty device + * @file: file object + * @buf: userspace buffer pointer + * @nr: size of I/O + * + * Write function of the terminal device. This is serialized with + * respect to other write callers but not to termios changes, reads + * and other such events. Since the receive code will echo characters, + * thus calling driver write methods, the output_lock is used in + * the output processing functions called here as well as in the + * echo processing function to protect the column state and space + * left in the buffer. + * + * This code must be sure never to sleep through a hangup. + * + * Locking: output_lock to protect column state and space left + * (note that the process_output*() functions take this + * lock themselves) + */ + +static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, + const unsigned char *buf, size_t nr) +{ + const unsigned char *b = buf; + DECLARE_WAITQUEUE(wait, current); + int c; + ssize_t retval = 0; + + /* Job control check -- must be done at start (POSIX.1 7.1.1.4). */ + if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) { + retval = tty_check_change(tty); + if (retval) + return retval; + } + + /* Write out any echoed characters that are still pending */ + process_echoes(tty); + + add_wait_queue(&tty->write_wait, &wait); + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) { + retval = -EIO; + break; + } + if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) { + while (nr > 0) { + ssize_t num = process_output_block(tty, b, nr); + if (num < 0) { + if (num == -EAGAIN) + break; + retval = num; + goto break_out; + } + b += num; + nr -= num; + if (nr == 0) + break; + c = *b; + if (process_output(c, tty) < 0) + break; + b++; nr--; + } + if (tty->ops->flush_chars) + tty->ops->flush_chars(tty); + } else { + while (nr > 0) { + c = tty->ops->write(tty, b, nr); + if (c < 0) { + retval = c; + goto break_out; + } + if (!c) + break; + b += c; + nr -= c; + } + } + if (!nr) + break; + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + schedule(); + } +break_out: + __set_current_state(TASK_RUNNING); + remove_wait_queue(&tty->write_wait, &wait); + if (b - buf != nr && tty->fasync) + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + return (b - buf) ? b - buf : retval; +} + +/** + * n_tty_poll - poll method for N_TTY + * @tty: terminal device + * @file: file accessing it + * @wait: poll table + * + * Called when the line discipline is asked to poll() for data or + * for special events. This code is not serialized with respect to + * other events save open/close. + * + * This code must be sure never to sleep through a hangup. + * Called without the kernel lock held - fine + */ + +static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, + poll_table *wait) +{ + unsigned int mask = 0; + + poll_wait(file, &tty->read_wait, wait); + poll_wait(file, &tty->write_wait, wait); + if (input_available_p(tty, TIME_CHAR(tty) ? 0 : MIN_CHAR(tty))) + mask |= POLLIN | POLLRDNORM; + if (tty->packet && tty->link->ctrl_status) + mask |= POLLPRI | POLLIN | POLLRDNORM; + if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) + mask |= POLLHUP; + if (tty_hung_up_p(file)) + mask |= POLLHUP; + if (!(mask & (POLLHUP | POLLIN | POLLRDNORM))) { + if (MIN_CHAR(tty) && !TIME_CHAR(tty)) + tty->minimum_to_wake = MIN_CHAR(tty); + else + tty->minimum_to_wake = 1; + } + if (tty->ops->write && !tty_is_writelocked(tty) && + tty_chars_in_buffer(tty) < WAKEUP_CHARS && + tty_write_room(tty) > 0) + mask |= POLLOUT | POLLWRNORM; + return mask; +} + +static unsigned long inq_canon(struct tty_struct *tty) +{ + int nr, head, tail; + + if (!tty->canon_data) + return 0; + head = tty->canon_head; + tail = tty->read_tail; + nr = (head - tail) & (N_TTY_BUF_SIZE-1); + /* Skip EOF-chars.. */ + while (head != tail) { + if (test_bit(tail, tty->read_flags) && + tty->read_buf[tail] == __DISABLED_CHAR) + nr--; + tail = (tail+1) & (N_TTY_BUF_SIZE-1); + } + return nr; +} + +static int n_tty_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int retval; + + switch (cmd) { + case TIOCOUTQ: + return put_user(tty_chars_in_buffer(tty), (int __user *) arg); + case TIOCINQ: + /* FIXME: Locking */ + retval = tty->read_cnt; + if (L_ICANON(tty)) + retval = inq_canon(tty); + return put_user(retval, (unsigned int __user *) arg); + default: + return n_tty_ioctl_helper(tty, file, cmd, arg); + } +} + +struct tty_ldisc_ops tty_ldisc_N_TTY = { + .magic = TTY_LDISC_MAGIC, + .name = "n_tty", + .open = n_tty_open, + .close = n_tty_close, + .flush_buffer = n_tty_flush_buffer, + .chars_in_buffer = n_tty_chars_in_buffer, + .read = n_tty_read, + .write = n_tty_write, + .ioctl = n_tty_ioctl, + .set_termios = n_tty_set_termios, + .poll = n_tty_poll, + .receive_buf = n_tty_receive_buf, + .write_wakeup = n_tty_write_wakeup +}; + +/** + * n_tty_inherit_ops - inherit N_TTY methods + * @ops: struct tty_ldisc_ops where to save N_TTY methods + * + * Used by a generic struct tty_ldisc_ops to easily inherit N_TTY + * methods. + */ + +void n_tty_inherit_ops(struct tty_ldisc_ops *ops) +{ + *ops = tty_ldisc_N_TTY; + ops->owner = NULL; + ops->refcount = ops->flags = 0; +} +EXPORT_SYMBOL_GPL(n_tty_inherit_ops); diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c new file mode 100644 index 000000000000..923a48585501 --- /dev/null +++ b/drivers/tty/pty.c @@ -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); diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c new file mode 100644 index 000000000000..eaa5d3efa79d --- /dev/null +++ b/drivers/tty/sysrq.c @@ -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); diff --git a/drivers/tty/tty_audit.c b/drivers/tty/tty_audit.c new file mode 100644 index 000000000000..f64582b0f623 --- /dev/null +++ b/drivers/tty/tty_audit.c @@ -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); + } +} diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c new file mode 100644 index 000000000000..cc1e9850d655 --- /dev/null +++ b/drivers/tty/tty_buffer.c @@ -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); +} + diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c new file mode 100644 index 000000000000..c05c5af5aa04 --- /dev/null +++ b/drivers/tty/tty_io.c @@ -0,0 +1,3263 @@ +/* + * linux/drivers/char/tty_io.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +/* + * 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles + * or rs-channels. It also implements echoing, cooked mode etc. + * + * Kill-line thanks to John T Kohl, who also corrected VMIN = VTIME = 0. + * + * Modified by Theodore Ts'o, 9/14/92, to dynamically allocate the + * tty_struct and tty_queue structures. Previously there was an array + * of 256 tty_struct's which was statically allocated, and the + * tty_queue structures were allocated at boot time. Both are now + * dynamically allocated only when the tty is open. + * + * Also restructured routines so that there is more of a separation + * between the high-level tty routines (tty_io.c and tty_ioctl.c) and + * the low-level tty routines (serial.c, pty.c, console.c). This + * makes for cleaner and more compact code. -TYT, 9/17/92 + * + * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines + * which can be dynamically activated and de-activated by the line + * discipline handling modules (like SLIP). + * + * NOTE: pay no attention to the line discipline code (yet); its + * interface is still subject to change in this version... + * -- TYT, 1/31/92 + * + * Added functionality to the OPOST tty handling. No delays, but all + * other bits should be there. + * -- Nick Holloway <alfie@dcs.warwick.ac.uk>, 27th May 1993. + * + * Rewrote canonical mode and added more termios flags. + * -- julian@uhunix.uhcc.hawaii.edu (J. Cowley), 13Jan94 + * + * Reorganized FASYNC support so mouse code can share it. + * -- ctm@ardi.com, 9Sep95 + * + * New TIOCLINUX variants added. + * -- mj@k332.feld.cvut.cz, 19-Nov-95 + * + * Restrict vt switching via ioctl() + * -- grif@cs.ucr.edu, 5-Dec-95 + * + * Move console and virtual terminal code to more appropriate files, + * implement CONFIG_VT and generalize console device interface. + * -- Marko Kohtala <Marko.Kohtala@hut.fi>, March 97 + * + * Rewrote tty_init_dev and tty_release_dev to eliminate races. + * -- Bill Hawes <whawes@star.net>, June 97 + * + * Added devfs support. + * -- C. Scott Ananian <cananian@alumni.princeton.edu>, 13-Jan-1998 + * + * Added support for a Unix98-style ptmx device. + * -- C. Scott Ananian <cananian@alumni.princeton.edu>, 14-Jan-1998 + * + * Reduced memory usage for older ARM systems + * -- Russell King <rmk@arm.linux.org.uk> + * + * Move do_SAK() into process context. Less stack use in devfs functions. + * alloc_tty_struct() always uses kmalloc() + * -- Andrew Morton <andrewm@uow.edu.eu> 17Mar01 + */ + +#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/fdtable.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/smp_lock.h> +#include <linux/device.h> +#include <linux/wait.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/seq_file.h> +#include <linux/serial.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/kmod.h> +#include <linux/nsproxy.h> + +#undef TTY_DEBUG_HANGUP + +#define TTY_PARANOIA_CHECK 1 +#define CHECK_TTY_COUNT 1 + +struct ktermios tty_std_termios = { /* for the benefit of tty drivers */ + .c_iflag = ICRNL | IXON, + .c_oflag = OPOST | ONLCR, + .c_cflag = B38400 | CS8 | CREAD | HUPCL, + .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | + ECHOCTL | ECHOKE | IEXTEN, + .c_cc = INIT_C_CC, + .c_ispeed = 38400, + .c_ospeed = 38400 +}; + +EXPORT_SYMBOL(tty_std_termios); + +/* This list gets poked at by procfs and various bits of boot up code. This + could do with some rationalisation such as pulling the tty proc function + into this file */ + +LIST_HEAD(tty_drivers); /* linked list of tty drivers */ + +/* Mutex to protect creating and releasing a tty. This is shared with + vt.c for deeply disgusting hack reasons */ +DEFINE_MUTEX(tty_mutex); +EXPORT_SYMBOL(tty_mutex); + +/* Spinlock to protect the tty->tty_files list */ +DEFINE_SPINLOCK(tty_files_lock); + +static ssize_t tty_read(struct file *, char __user *, size_t, loff_t *); +static ssize_t tty_write(struct file *, const char __user *, size_t, loff_t *); +ssize_t redirected_tty_write(struct file *, const char __user *, + size_t, loff_t *); +static unsigned int tty_poll(struct file *, poll_table *); +static int tty_open(struct inode *, struct file *); +long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +#ifdef CONFIG_COMPAT +static long tty_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); +#else +#define tty_compat_ioctl NULL +#endif +static int __tty_fasync(int fd, struct file *filp, int on); +static int tty_fasync(int fd, struct file *filp, int on); +static void release_tty(struct tty_struct *tty, int idx); +static void __proc_set_tty(struct task_struct *tsk, struct tty_struct *tty); +static void proc_set_tty(struct task_struct *tsk, struct tty_struct *tty); + +/** + * alloc_tty_struct - allocate a tty object + * + * Return a new empty tty structure. The data fields have not + * been initialized in any way but has been zeroed + * + * Locking: none + */ + +struct tty_struct *alloc_tty_struct(void) +{ + return kzalloc(sizeof(struct tty_struct), GFP_KERNEL); +} + +/** + * free_tty_struct - free a disused tty + * @tty: tty struct to free + * + * Free the write buffers, tty queue and tty memory itself. + * + * Locking: none. Must be called after tty is definitely unused + */ + +void free_tty_struct(struct tty_struct *tty) +{ + if (tty->dev) + put_device(tty->dev); + kfree(tty->write_buf); + tty_buffer_free_all(tty); + kfree(tty); +} + +static inline struct tty_struct *file_tty(struct file *file) +{ + return ((struct tty_file_private *)file->private_data)->tty; +} + +/* Associate a new file with the tty structure */ +int tty_add_file(struct tty_struct *tty, struct file *file) +{ + struct tty_file_private *priv; + + priv = kmalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->tty = tty; + priv->file = file; + file->private_data = priv; + + spin_lock(&tty_files_lock); + list_add(&priv->list, &tty->tty_files); + spin_unlock(&tty_files_lock); + + return 0; +} + +/* Delete file from its tty */ +void tty_del_file(struct file *file) +{ + struct tty_file_private *priv = file->private_data; + + spin_lock(&tty_files_lock); + list_del(&priv->list); + spin_unlock(&tty_files_lock); + file->private_data = NULL; + kfree(priv); +} + + +#define TTY_NUMBER(tty) ((tty)->index + (tty)->driver->name_base) + +/** + * tty_name - return tty naming + * @tty: tty structure + * @buf: buffer for output + * + * Convert a tty structure into a name. The name reflects the kernel + * naming policy and if udev is in use may not reflect user space + * + * Locking: none + */ + +char *tty_name(struct tty_struct *tty, char *buf) +{ + if (!tty) /* Hmm. NULL pointer. That's fun. */ + strcpy(buf, "NULL tty"); + else + strcpy(buf, tty->name); + return buf; +} + +EXPORT_SYMBOL(tty_name); + +int tty_paranoia_check(struct tty_struct *tty, struct inode *inode, + const char *routine) +{ +#ifdef TTY_PARANOIA_CHECK + if (!tty) { + printk(KERN_WARNING + "null TTY for (%d:%d) in %s\n", + imajor(inode), iminor(inode), routine); + return 1; + } + if (tty->magic != TTY_MAGIC) { + printk(KERN_WARNING + "bad magic number for tty struct (%d:%d) in %s\n", + imajor(inode), iminor(inode), routine); + return 1; + } +#endif + return 0; +} + +static int check_tty_count(struct tty_struct *tty, const char *routine) +{ +#ifdef CHECK_TTY_COUNT + struct list_head *p; + int count = 0; + + spin_lock(&tty_files_lock); + list_for_each(p, &tty->tty_files) { + count++; + } + spin_unlock(&tty_files_lock); + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_SLAVE && + tty->link && tty->link->count) + count++; + if (tty->count != count) { + printk(KERN_WARNING "Warning: dev (%s) tty->count(%d) " + "!= #fd's(%d) in %s\n", + tty->name, tty->count, count, routine); + return count; + } +#endif + return 0; +} + +/** + * get_tty_driver - find device of a tty + * @dev_t: device identifier + * @index: returns the index of the tty + * + * This routine returns a tty driver structure, given a device number + * and also passes back the index number. + * + * Locking: caller must hold tty_mutex + */ + +static struct tty_driver *get_tty_driver(dev_t device, int *index) +{ + struct tty_driver *p; + + list_for_each_entry(p, &tty_drivers, tty_drivers) { + dev_t base = MKDEV(p->major, p->minor_start); + if (device < base || device >= base + p->num) + continue; + *index = device - base; + return tty_driver_kref_get(p); + } + return NULL; +} + +#ifdef CONFIG_CONSOLE_POLL + +/** + * tty_find_polling_driver - find device of a polled tty + * @name: name string to match + * @line: pointer to resulting tty line nr + * + * This routine returns a tty driver structure, given a name + * and the condition that the tty driver is capable of polled + * operation. + */ +struct tty_driver *tty_find_polling_driver(char *name, int *line) +{ + struct tty_driver *p, *res = NULL; + int tty_line = 0; + int len; + char *str, *stp; + + for (str = name; *str; str++) + if ((*str >= '0' && *str <= '9') || *str == ',') + break; + if (!*str) + return NULL; + + len = str - name; + tty_line = simple_strtoul(str, &str, 10); + + mutex_lock(&tty_mutex); + /* Search through the tty devices to look for a match */ + list_for_each_entry(p, &tty_drivers, tty_drivers) { + if (strncmp(name, p->name, len) != 0) + continue; + stp = str; + if (*stp == ',') + stp++; + if (*stp == '\0') + stp = NULL; + + if (tty_line >= 0 && tty_line < p->num && p->ops && + p->ops->poll_init && !p->ops->poll_init(p, tty_line, stp)) { + res = tty_driver_kref_get(p); + *line = tty_line; + break; + } + } + mutex_unlock(&tty_mutex); + + return res; +} +EXPORT_SYMBOL_GPL(tty_find_polling_driver); +#endif + +/** + * tty_check_change - check for POSIX terminal changes + * @tty: tty to check + * + * If we try to write to, or set the state of, a terminal and we're + * not in the foreground, send a SIGTTOU. If the signal is blocked or + * ignored, go ahead and perform the operation. (POSIX 7.2) + * + * Locking: ctrl_lock + */ + +int tty_check_change(struct tty_struct *tty) +{ + unsigned long flags; + int ret = 0; + + if (current->signal->tty != tty) + return 0; + + spin_lock_irqsave(&tty->ctrl_lock, flags); + + if (!tty->pgrp) { + printk(KERN_WARNING "tty_check_change: tty->pgrp == NULL!\n"); + goto out_unlock; + } + if (task_pgrp(current) == tty->pgrp) + goto out_unlock; + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + if (is_ignored(SIGTTOU)) + goto out; + if (is_current_pgrp_orphaned()) { + ret = -EIO; + goto out; + } + kill_pgrp(task_pgrp(current), SIGTTOU, 1); + set_thread_flag(TIF_SIGPENDING); + ret = -ERESTARTSYS; +out: + return ret; +out_unlock: + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + return ret; +} + +EXPORT_SYMBOL(tty_check_change); + +static ssize_t hung_up_tty_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +static ssize_t hung_up_tty_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return -EIO; +} + +/* No kernel lock held - none needed ;) */ +static unsigned int hung_up_tty_poll(struct file *filp, poll_table *wait) +{ + return POLLIN | POLLOUT | POLLERR | POLLHUP | POLLRDNORM | POLLWRNORM; +} + +static long hung_up_tty_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return cmd == TIOCSPGRP ? -ENOTTY : -EIO; +} + +static long hung_up_tty_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return cmd == TIOCSPGRP ? -ENOTTY : -EIO; +} + +static const struct file_operations tty_fops = { + .llseek = no_llseek, + .read = tty_read, + .write = tty_write, + .poll = tty_poll, + .unlocked_ioctl = tty_ioctl, + .compat_ioctl = tty_compat_ioctl, + .open = tty_open, + .release = tty_release, + .fasync = tty_fasync, +}; + +static const struct file_operations console_fops = { + .llseek = no_llseek, + .read = tty_read, + .write = redirected_tty_write, + .poll = tty_poll, + .unlocked_ioctl = tty_ioctl, + .compat_ioctl = tty_compat_ioctl, + .open = tty_open, + .release = tty_release, + .fasync = tty_fasync, +}; + +static const struct file_operations hung_up_tty_fops = { + .llseek = no_llseek, + .read = hung_up_tty_read, + .write = hung_up_tty_write, + .poll = hung_up_tty_poll, + .unlocked_ioctl = hung_up_tty_ioctl, + .compat_ioctl = hung_up_tty_compat_ioctl, + .release = tty_release, +}; + +static DEFINE_SPINLOCK(redirect_lock); +static struct file *redirect; + +/** + * tty_wakeup - request more data + * @tty: terminal + * + * Internal and external helper for wakeups of tty. This function + * informs the line discipline if present that the driver is ready + * to receive more output data. + */ + +void tty_wakeup(struct tty_struct *tty) +{ + struct tty_ldisc *ld; + + if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) { + ld = tty_ldisc_ref(tty); + if (ld) { + if (ld->ops->write_wakeup) + ld->ops->write_wakeup(tty); + tty_ldisc_deref(ld); + } + } + wake_up_interruptible_poll(&tty->write_wait, POLLOUT); +} + +EXPORT_SYMBOL_GPL(tty_wakeup); + +/** + * __tty_hangup - actual handler for hangup events + * @work: tty device + * + * This can be called by the "eventd" kernel thread. That is process + * synchronous but doesn't hold any locks, so we need to make sure we + * have the appropriate locks for what we're doing. + * + * The hangup event clears any pending redirections onto the hung up + * device. It ensures future writes will error and it does the needed + * line discipline hangup and signal delivery. The tty object itself + * remains intact. + * + * Locking: + * BTM + * redirect lock for undoing redirection + * file list lock for manipulating list of ttys + * tty_ldisc_lock from called functions + * termios_mutex resetting termios data + * tasklist_lock to walk task list for hangup event + * ->siglock to protect ->signal/->sighand + */ +void __tty_hangup(struct tty_struct *tty) +{ + struct file *cons_filp = NULL; + struct file *filp, *f = NULL; + struct task_struct *p; + struct tty_file_private *priv; + int closecount = 0, n; + unsigned long flags; + int refs = 0; + + if (!tty) + return; + + + spin_lock(&redirect_lock); + if (redirect && file_tty(redirect) == tty) { + f = redirect; + redirect = NULL; + } + spin_unlock(&redirect_lock); + + tty_lock(); + + /* inuse_filps is protected by the single tty lock, + this really needs to change if we want to flush the + workqueue with the lock held */ + check_tty_count(tty, "tty_hangup"); + + spin_lock(&tty_files_lock); + /* This breaks for file handles being sent over AF_UNIX sockets ? */ + list_for_each_entry(priv, &tty->tty_files, list) { + filp = priv->file; + if (filp->f_op->write == redirected_tty_write) + cons_filp = filp; + if (filp->f_op->write != tty_write) + continue; + closecount++; + __tty_fasync(-1, filp, 0); /* can't block */ + filp->f_op = &hung_up_tty_fops; + } + spin_unlock(&tty_files_lock); + + tty_ldisc_hangup(tty); + + read_lock(&tasklist_lock); + if (tty->session) { + do_each_pid_task(tty->session, PIDTYPE_SID, p) { + spin_lock_irq(&p->sighand->siglock); + if (p->signal->tty == tty) { + p->signal->tty = NULL; + /* We defer the dereferences outside fo + the tasklist lock */ + refs++; + } + if (!p->signal->leader) { + spin_unlock_irq(&p->sighand->siglock); + continue; + } + __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p); + __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p); + put_pid(p->signal->tty_old_pgrp); /* A noop */ + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (tty->pgrp) + p->signal->tty_old_pgrp = get_pid(tty->pgrp); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + spin_unlock_irq(&p->sighand->siglock); + } while_each_pid_task(tty->session, PIDTYPE_SID, p); + } + read_unlock(&tasklist_lock); + + spin_lock_irqsave(&tty->ctrl_lock, flags); + clear_bit(TTY_THROTTLED, &tty->flags); + clear_bit(TTY_PUSH, &tty->flags); + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + put_pid(tty->session); + put_pid(tty->pgrp); + tty->session = NULL; + tty->pgrp = NULL; + tty->ctrl_status = 0; + set_bit(TTY_HUPPED, &tty->flags); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + + /* Account for the p->signal references we killed */ + while (refs--) + tty_kref_put(tty); + + /* + * If one of the devices matches a console pointer, we + * cannot just call hangup() because that will cause + * tty->count and state->count to go out of sync. + * So we just call close() the right number of times. + */ + if (cons_filp) { + if (tty->ops->close) + for (n = 0; n < closecount; n++) + tty->ops->close(tty, cons_filp); + } else if (tty->ops->hangup) + (tty->ops->hangup)(tty); + /* + * We don't want to have driver/ldisc interactions beyond + * the ones we did here. The driver layer expects no + * calls after ->hangup() from the ldisc side. However we + * can't yet guarantee all that. + */ + set_bit(TTY_HUPPED, &tty->flags); + tty_ldisc_enable(tty); + + tty_unlock(); + + if (f) + fput(f); +} + +static void do_tty_hangup(struct work_struct *work) +{ + struct tty_struct *tty = + container_of(work, struct tty_struct, hangup_work); + + __tty_hangup(tty); +} + +/** + * tty_hangup - trigger a hangup event + * @tty: tty to hangup + * + * A carrier loss (virtual or otherwise) has occurred on this like + * schedule a hangup sequence to run after this event. + */ + +void tty_hangup(struct tty_struct *tty) +{ +#ifdef TTY_DEBUG_HANGUP + char buf[64]; + printk(KERN_DEBUG "%s hangup...\n", tty_name(tty, buf)); +#endif + schedule_work(&tty->hangup_work); +} + +EXPORT_SYMBOL(tty_hangup); + +/** + * tty_vhangup - process vhangup + * @tty: tty to hangup + * + * The user has asked via system call for the terminal to be hung up. + * We do this synchronously so that when the syscall returns the process + * is complete. That guarantee is necessary for security reasons. + */ + +void tty_vhangup(struct tty_struct *tty) +{ +#ifdef TTY_DEBUG_HANGUP + char buf[64]; + + printk(KERN_DEBUG "%s vhangup...\n", tty_name(tty, buf)); +#endif + __tty_hangup(tty); +} + +EXPORT_SYMBOL(tty_vhangup); + + +/** + * tty_vhangup_self - process vhangup for own ctty + * + * Perform a vhangup on the current controlling tty + */ + +void tty_vhangup_self(void) +{ + struct tty_struct *tty; + + tty = get_current_tty(); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } +} + +/** + * tty_hung_up_p - was tty hung up + * @filp: file pointer of tty + * + * Return true if the tty has been subject to a vhangup or a carrier + * loss + */ + +int tty_hung_up_p(struct file *filp) +{ + return (filp->f_op == &hung_up_tty_fops); +} + +EXPORT_SYMBOL(tty_hung_up_p); + +static void session_clear_tty(struct pid *session) +{ + struct task_struct *p; + do_each_pid_task(session, PIDTYPE_SID, p) { + proc_clear_tty(p); + } while_each_pid_task(session, PIDTYPE_SID, p); +} + +/** + * disassociate_ctty - disconnect controlling tty + * @on_exit: true if exiting so need to "hang up" the session + * + * This function is typically called only by the session leader, when + * it wants to disassociate itself from its controlling tty. + * + * It performs the following functions: + * (1) Sends a SIGHUP and SIGCONT to the foreground process group + * (2) Clears the tty from being controlling the session + * (3) Clears the controlling tty for all processes in the + * session group. + * + * The argument on_exit is set to 1 if called when a process is + * exiting; it is 0 if called by the ioctl TIOCNOTTY. + * + * Locking: + * BTM is taken for hysterical raisins, and held when + * called from no_tty(). + * tty_mutex is taken to protect tty + * ->siglock is taken to protect ->signal/->sighand + * tasklist_lock is taken to walk process list for sessions + * ->siglock is taken to protect ->signal/->sighand + */ + +void disassociate_ctty(int on_exit) +{ + struct tty_struct *tty; + struct pid *tty_pgrp = NULL; + + if (!current->signal->leader) + return; + + tty = get_current_tty(); + if (tty) { + tty_pgrp = get_pid(tty->pgrp); + if (on_exit) { + if (tty->driver->type != TTY_DRIVER_TYPE_PTY) + tty_vhangup(tty); + } + tty_kref_put(tty); + } else if (on_exit) { + struct pid *old_pgrp; + spin_lock_irq(¤t->sighand->siglock); + old_pgrp = current->signal->tty_old_pgrp; + current->signal->tty_old_pgrp = NULL; + spin_unlock_irq(¤t->sighand->siglock); + if (old_pgrp) { + kill_pgrp(old_pgrp, SIGHUP, on_exit); + kill_pgrp(old_pgrp, SIGCONT, on_exit); + put_pid(old_pgrp); + } + return; + } + if (tty_pgrp) { + kill_pgrp(tty_pgrp, SIGHUP, on_exit); + if (!on_exit) + kill_pgrp(tty_pgrp, SIGCONT, on_exit); + put_pid(tty_pgrp); + } + + spin_lock_irq(¤t->sighand->siglock); + put_pid(current->signal->tty_old_pgrp); + current->signal->tty_old_pgrp = NULL; + spin_unlock_irq(¤t->sighand->siglock); + + tty = get_current_tty(); + if (tty) { + unsigned long flags; + spin_lock_irqsave(&tty->ctrl_lock, flags); + put_pid(tty->session); + put_pid(tty->pgrp); + tty->session = NULL; + tty->pgrp = NULL; + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + tty_kref_put(tty); + } else { +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "error attempted to write to tty [0x%p]" + " = NULL", tty); +#endif + } + + /* Now clear signal->tty under the lock */ + read_lock(&tasklist_lock); + session_clear_tty(task_session(current)); + read_unlock(&tasklist_lock); +} + +/** + * + * no_tty - Ensure the current process does not have a controlling tty + */ +void no_tty(void) +{ + struct task_struct *tsk = current; + tty_lock(); + disassociate_ctty(0); + tty_unlock(); + proc_clear_tty(tsk); +} + + +/** + * stop_tty - propagate flow control + * @tty: tty to stop + * + * Perform flow control to the driver. For PTY/TTY pairs we + * must also propagate the TIOCKPKT status. May be called + * on an already stopped device and will not re-call the driver + * method. + * + * This functionality is used by both the line disciplines for + * halting incoming flow and by the driver. It may therefore be + * called from any context, may be under the tty atomic_write_lock + * but not always. + * + * Locking: + * Uses the tty control lock internally + */ + +void stop_tty(struct tty_struct *tty) +{ + unsigned long flags; + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (tty->stopped) { + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + return; + } + tty->stopped = 1; + if (tty->link && tty->link->packet) { + tty->ctrl_status &= ~TIOCPKT_START; + tty->ctrl_status |= TIOCPKT_STOP; + wake_up_interruptible_poll(&tty->link->read_wait, POLLIN); + } + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + if (tty->ops->stop) + (tty->ops->stop)(tty); +} + +EXPORT_SYMBOL(stop_tty); + +/** + * start_tty - propagate flow control + * @tty: tty to start + * + * Start a tty that has been stopped if at all possible. Perform + * any necessary wakeups and propagate the TIOCPKT status. If this + * is the tty was previous stopped and is being started then the + * driver start method is invoked and the line discipline woken. + * + * Locking: + * ctrl_lock + */ + +void start_tty(struct tty_struct *tty) +{ + unsigned long flags; + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (!tty->stopped || tty->flow_stopped) { + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + return; + } + tty->stopped = 0; + if (tty->link && tty->link->packet) { + tty->ctrl_status &= ~TIOCPKT_STOP; + tty->ctrl_status |= TIOCPKT_START; + wake_up_interruptible_poll(&tty->link->read_wait, POLLIN); + } + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + if (tty->ops->start) + (tty->ops->start)(tty); + /* If we have a running line discipline it may need kicking */ + tty_wakeup(tty); +} + +EXPORT_SYMBOL(start_tty); + +/** + * tty_read - read method for tty device files + * @file: pointer to tty file + * @buf: user buffer + * @count: size of user buffer + * @ppos: unused + * + * Perform the read system call function on this terminal device. Checks + * for hung up devices before calling the line discipline method. + * + * Locking: + * Locks the line discipline internally while needed. Multiple + * read calls may be outstanding in parallel. + */ + +static ssize_t tty_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int i; + struct inode *inode = file->f_path.dentry->d_inode; + struct tty_struct *tty = file_tty(file); + struct tty_ldisc *ld; + + if (tty_paranoia_check(tty, inode, "tty_read")) + return -EIO; + if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags))) + return -EIO; + + /* We want to wait for the line discipline to sort out in this + situation */ + ld = tty_ldisc_ref_wait(tty); + if (ld->ops->read) + i = (ld->ops->read)(tty, file, buf, count); + else + i = -EIO; + tty_ldisc_deref(ld); + if (i > 0) + inode->i_atime = current_fs_time(inode->i_sb); + return i; +} + +void tty_write_unlock(struct tty_struct *tty) +{ + mutex_unlock(&tty->atomic_write_lock); + wake_up_interruptible_poll(&tty->write_wait, POLLOUT); +} + +int tty_write_lock(struct tty_struct *tty, int ndelay) +{ + if (!mutex_trylock(&tty->atomic_write_lock)) { + if (ndelay) + return -EAGAIN; + if (mutex_lock_interruptible(&tty->atomic_write_lock)) + return -ERESTARTSYS; + } + return 0; +} + +/* + * Split writes up in sane blocksizes to avoid + * denial-of-service type attacks + */ +static inline ssize_t do_tty_write( + ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t), + struct tty_struct *tty, + struct file *file, + const char __user *buf, + size_t count) +{ + ssize_t ret, written = 0; + unsigned int chunk; + + ret = tty_write_lock(tty, file->f_flags & O_NDELAY); + if (ret < 0) + return ret; + + /* + * We chunk up writes into a temporary buffer. This + * simplifies low-level drivers immensely, since they + * don't have locking issues and user mode accesses. + * + * But if TTY_NO_WRITE_SPLIT is set, we should use a + * big chunk-size.. + * + * The default chunk-size is 2kB, because the NTTY + * layer has problems with bigger chunks. It will + * claim to be able to handle more characters than + * it actually does. + * + * FIXME: This can probably go away now except that 64K chunks + * are too likely to fail unless switched to vmalloc... + */ + chunk = 2048; + if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags)) + chunk = 65536; + if (count < chunk) + chunk = count; + + /* write_buf/write_cnt is protected by the atomic_write_lock mutex */ + if (tty->write_cnt < chunk) { + unsigned char *buf_chunk; + + if (chunk < 1024) + chunk = 1024; + + buf_chunk = kmalloc(chunk, GFP_KERNEL); + if (!buf_chunk) { + ret = -ENOMEM; + goto out; + } + kfree(tty->write_buf); + tty->write_cnt = chunk; + tty->write_buf = buf_chunk; + } + + /* Do the write .. */ + for (;;) { + size_t size = count; + if (size > chunk) + size = chunk; + ret = -EFAULT; + if (copy_from_user(tty->write_buf, buf, size)) + break; + ret = write(tty, file, tty->write_buf, size); + if (ret <= 0) + break; + written += ret; + buf += ret; + count -= ret; + if (!count) + break; + ret = -ERESTARTSYS; + if (signal_pending(current)) + break; + cond_resched(); + } + if (written) { + struct inode *inode = file->f_path.dentry->d_inode; + inode->i_mtime = current_fs_time(inode->i_sb); + ret = written; + } +out: + tty_write_unlock(tty); + return ret; +} + +/** + * tty_write_message - write a message to a certain tty, not just the console. + * @tty: the destination tty_struct + * @msg: the message to write + * + * This is used for messages that need to be redirected to a specific tty. + * We don't put it into the syslog queue right now maybe in the future if + * really needed. + * + * We must still hold the BTM and test the CLOSING flag for the moment. + */ + +void tty_write_message(struct tty_struct *tty, char *msg) +{ + if (tty) { + mutex_lock(&tty->atomic_write_lock); + tty_lock(); + if (tty->ops->write && !test_bit(TTY_CLOSING, &tty->flags)) { + tty_unlock(); + tty->ops->write(tty, msg, strlen(msg)); + } else + tty_unlock(); + tty_write_unlock(tty); + } + return; +} + + +/** + * tty_write - write method for tty device file + * @file: tty file pointer + * @buf: user data to write + * @count: bytes to write + * @ppos: unused + * + * Write data to a tty device via the line discipline. + * + * Locking: + * Locks the line discipline as required + * Writes to the tty driver are serialized by the atomic_write_lock + * and are then processed in chunks to the device. The line discipline + * write method will not be invoked in parallel for each device. + */ + +static ssize_t tty_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode *inode = file->f_path.dentry->d_inode; + struct tty_struct *tty = file_tty(file); + struct tty_ldisc *ld; + ssize_t ret; + + if (tty_paranoia_check(tty, inode, "tty_write")) + return -EIO; + if (!tty || !tty->ops->write || + (test_bit(TTY_IO_ERROR, &tty->flags))) + return -EIO; + /* Short term debug to catch buggy drivers */ + if (tty->ops->write_room == NULL) + printk(KERN_ERR "tty driver %s lacks a write_room method.\n", + tty->driver->name); + ld = tty_ldisc_ref_wait(tty); + if (!ld->ops->write) + ret = -EIO; + else + ret = do_tty_write(ld->ops->write, tty, file, buf, count); + tty_ldisc_deref(ld); + return ret; +} + +ssize_t redirected_tty_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct file *p = NULL; + + spin_lock(&redirect_lock); + if (redirect) { + get_file(redirect); + p = redirect; + } + spin_unlock(&redirect_lock); + + if (p) { + ssize_t res; + res = vfs_write(p, buf, count, &p->f_pos); + fput(p); + return res; + } + return tty_write(file, buf, count, ppos); +} + +static char ptychar[] = "pqrstuvwxyzabcde"; + +/** + * pty_line_name - generate name for a pty + * @driver: the tty driver in use + * @index: the minor number + * @p: output buffer of at least 6 bytes + * + * Generate a name from a driver reference and write it to the output + * buffer. + * + * Locking: None + */ +static void pty_line_name(struct tty_driver *driver, int index, char *p) +{ + int i = index + driver->name_base; + /* ->name is initialized to "ttyp", but "tty" is expected */ + sprintf(p, "%s%c%x", + driver->subtype == PTY_TYPE_SLAVE ? "tty" : driver->name, + ptychar[i >> 4 & 0xf], i & 0xf); +} + +/** + * tty_line_name - generate name for a tty + * @driver: the tty driver in use + * @index: the minor number + * @p: output buffer of at least 7 bytes + * + * Generate a name from a driver reference and write it to the output + * buffer. + * + * Locking: None + */ +static void tty_line_name(struct tty_driver *driver, int index, char *p) +{ + sprintf(p, "%s%d", driver->name, index + driver->name_base); +} + +/** + * tty_driver_lookup_tty() - find an existing tty, if any + * @driver: the driver for the tty + * @idx: the minor number + * + * Return the tty, if found or ERR_PTR() otherwise. + * + * Locking: tty_mutex must be held. If tty is found, the mutex must + * be held until the 'fast-open' is also done. Will change once we + * have refcounting in the driver and per driver locking + */ +static struct tty_struct *tty_driver_lookup_tty(struct tty_driver *driver, + struct inode *inode, int idx) +{ + struct tty_struct *tty; + + if (driver->ops->lookup) + return driver->ops->lookup(driver, inode, idx); + + tty = driver->ttys[idx]; + return tty; +} + +/** + * tty_init_termios - helper for termios setup + * @tty: the tty to set up + * + * Initialise the termios structures for this tty. Thus runs under + * the tty_mutex currently so we can be relaxed about ordering. + */ + +int tty_init_termios(struct tty_struct *tty) +{ + struct ktermios *tp; + int idx = tty->index; + + tp = tty->driver->termios[idx]; + if (tp == NULL) { + tp = kzalloc(sizeof(struct ktermios[2]), GFP_KERNEL); + if (tp == NULL) + return -ENOMEM; + memcpy(tp, &tty->driver->init_termios, + sizeof(struct ktermios)); + tty->driver->termios[idx] = tp; + } + tty->termios = tp; + tty->termios_locked = tp + 1; + + /* Compatibility until drivers always set this */ + tty->termios->c_ispeed = tty_termios_input_baud_rate(tty->termios); + tty->termios->c_ospeed = tty_termios_baud_rate(tty->termios); + return 0; +} +EXPORT_SYMBOL_GPL(tty_init_termios); + +/** + * tty_driver_install_tty() - install a tty entry in the driver + * @driver: the driver for the tty + * @tty: the tty + * + * Install a tty object into the driver tables. The tty->index field + * will be set by the time this is called. This method is responsible + * for ensuring any need additional structures are allocated and + * configured. + * + * Locking: tty_mutex for now + */ +static int tty_driver_install_tty(struct tty_driver *driver, + struct tty_struct *tty) +{ + int idx = tty->index; + int ret; + + if (driver->ops->install) { + ret = driver->ops->install(driver, tty); + return ret; + } + + if (tty_init_termios(tty) == 0) { + tty_driver_kref_get(driver); + tty->count++; + driver->ttys[idx] = tty; + return 0; + } + return -ENOMEM; +} + +/** + * tty_driver_remove_tty() - remove a tty from the driver tables + * @driver: the driver for the tty + * @idx: the minor number + * + * Remvoe a tty object from the driver tables. The tty->index field + * will be set by the time this is called. + * + * Locking: tty_mutex for now + */ +static void tty_driver_remove_tty(struct tty_driver *driver, + struct tty_struct *tty) +{ + if (driver->ops->remove) + driver->ops->remove(driver, tty); + else + driver->ttys[tty->index] = NULL; +} + +/* + * tty_reopen() - fast re-open of an open tty + * @tty - the tty to open + * + * Return 0 on success, -errno on error. + * + * Locking: tty_mutex must be held from the time the tty was found + * till this open completes. + */ +static int tty_reopen(struct tty_struct *tty) +{ + struct tty_driver *driver = tty->driver; + + if (test_bit(TTY_CLOSING, &tty->flags)) + return -EIO; + + if (driver->type == TTY_DRIVER_TYPE_PTY && + driver->subtype == PTY_TYPE_MASTER) { + /* + * special case for PTY masters: only one open permitted, + * and the slave side open count is incremented as well. + */ + if (tty->count) + return -EIO; + + tty->link->count++; + } + tty->count++; + tty->driver = driver; /* N.B. why do this every time?? */ + + mutex_lock(&tty->ldisc_mutex); + WARN_ON(!test_bit(TTY_LDISC, &tty->flags)); + mutex_unlock(&tty->ldisc_mutex); + + return 0; +} + +/** + * tty_init_dev - initialise a tty device + * @driver: tty driver we are opening a device on + * @idx: device index + * @ret_tty: returned tty structure + * @first_ok: ok to open a new device (used by ptmx) + * + * Prepare a tty device. This may not be a "new" clean device but + * could also be an active device. The pty drivers require special + * handling because of this. + * + * Locking: + * The function is called under the tty_mutex, which + * protects us from the tty struct or driver itself going away. + * + * On exit the tty device has the line discipline attached and + * a reference count of 1. If a pair was created for pty/tty use + * and the other was a pty master then it too has a reference count of 1. + * + * WSH 06/09/97: Rewritten to remove races and properly clean up after a + * failed open. The new code protects the open with a mutex, so it's + * really quite straightforward. The mutex locking can probably be + * relaxed for the (most common) case of reopening a tty. + */ + +struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx, + int first_ok) +{ + struct tty_struct *tty; + int retval; + + /* Check if pty master is being opened multiple times */ + if (driver->subtype == PTY_TYPE_MASTER && + (driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok) { + return ERR_PTR(-EIO); + } + + /* + * First time open is complex, especially for PTY devices. + * This code guarantees that either everything succeeds and the + * TTY is ready for operation, or else the table slots are vacated + * and the allocated memory released. (Except that the termios + * and locked termios may be retained.) + */ + + if (!try_module_get(driver->owner)) + return ERR_PTR(-ENODEV); + + tty = alloc_tty_struct(); + if (!tty) + goto fail_no_mem; + initialize_tty_struct(tty, driver, idx); + + retval = tty_driver_install_tty(driver, tty); + if (retval < 0) { + free_tty_struct(tty); + module_put(driver->owner); + return ERR_PTR(retval); + } + + /* + * Structures all installed ... call the ldisc open routines. + * If we fail here just call release_tty to clean up. No need + * to decrement the use counts, as release_tty doesn't care. + */ + retval = tty_ldisc_setup(tty, tty->link); + if (retval) + goto release_mem_out; + return tty; + +fail_no_mem: + module_put(driver->owner); + return ERR_PTR(-ENOMEM); + + /* call the tty release_tty routine to clean out this slot */ +release_mem_out: + if (printk_ratelimit()) + printk(KERN_INFO "tty_init_dev: ldisc open failed, " + "clearing slot %d\n", idx); + release_tty(tty, idx); + return ERR_PTR(retval); +} + +void tty_free_termios(struct tty_struct *tty) +{ + struct ktermios *tp; + int idx = tty->index; + /* Kill this flag and push into drivers for locking etc */ + if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) { + /* FIXME: Locking on ->termios array */ + tp = tty->termios; + tty->driver->termios[idx] = NULL; + kfree(tp); + } +} +EXPORT_SYMBOL(tty_free_termios); + +void tty_shutdown(struct tty_struct *tty) +{ + tty_driver_remove_tty(tty->driver, tty); + tty_free_termios(tty); +} +EXPORT_SYMBOL(tty_shutdown); + +/** + * release_one_tty - release tty structure memory + * @kref: kref of tty we are obliterating + * + * Releases memory associated with a tty structure, and clears out the + * driver table slots. This function is called when a device is no longer + * in use. It also gets called when setup of a device fails. + * + * Locking: + * tty_mutex - sometimes only + * takes the file list lock internally when working on the list + * of ttys that the driver keeps. + * + * This method gets called from a work queue so that the driver private + * cleanup ops can sleep (needed for USB at least) + */ +static void release_one_tty(struct work_struct *work) +{ + struct tty_struct *tty = + container_of(work, struct tty_struct, hangup_work); + struct tty_driver *driver = tty->driver; + + if (tty->ops->cleanup) + tty->ops->cleanup(tty); + + tty->magic = 0; + tty_driver_kref_put(driver); + module_put(driver->owner); + + spin_lock(&tty_files_lock); + list_del_init(&tty->tty_files); + spin_unlock(&tty_files_lock); + + put_pid(tty->pgrp); + put_pid(tty->session); + free_tty_struct(tty); +} + +static void queue_release_one_tty(struct kref *kref) +{ + struct tty_struct *tty = container_of(kref, struct tty_struct, kref); + + if (tty->ops->shutdown) + tty->ops->shutdown(tty); + else + tty_shutdown(tty); + + /* The hangup queue is now free so we can reuse it rather than + waste a chunk of memory for each port */ + INIT_WORK(&tty->hangup_work, release_one_tty); + schedule_work(&tty->hangup_work); +} + +/** + * tty_kref_put - release a tty kref + * @tty: tty device + * + * Release a reference to a tty device and if need be let the kref + * layer destruct the object for us + */ + +void tty_kref_put(struct tty_struct *tty) +{ + if (tty) + kref_put(&tty->kref, queue_release_one_tty); +} +EXPORT_SYMBOL(tty_kref_put); + +/** + * release_tty - release tty structure memory + * + * Release both @tty and a possible linked partner (think pty pair), + * and decrement the refcount of the backing module. + * + * Locking: + * tty_mutex - sometimes only + * takes the file list lock internally when working on the list + * of ttys that the driver keeps. + * FIXME: should we require tty_mutex is held here ?? + * + */ +static void release_tty(struct tty_struct *tty, int idx) +{ + /* This should always be true but check for the moment */ + WARN_ON(tty->index != idx); + + if (tty->link) + tty_kref_put(tty->link); + tty_kref_put(tty); +} + +/** + * tty_release - vfs callback for close + * @inode: inode of tty + * @filp: file pointer for handle to tty + * + * Called the last time each file handle is closed that references + * this tty. There may however be several such references. + * + * Locking: + * Takes bkl. See tty_release_dev + * + * Even releasing the tty structures is a tricky business.. We have + * to be very careful that the structures are all released at the + * same time, as interrupts might otherwise get the wrong pointers. + * + * WSH 09/09/97: rewritten to avoid some nasty race conditions that could + * lead to double frees or releasing memory still in use. + */ + +int tty_release(struct inode *inode, struct file *filp) +{ + struct tty_struct *tty = file_tty(filp); + struct tty_struct *o_tty; + int pty_master, tty_closing, o_tty_closing, do_sleep; + int devpts; + int idx; + char buf[64]; + + if (tty_paranoia_check(tty, inode, "tty_release_dev")) + return 0; + + tty_lock(); + check_tty_count(tty, "tty_release_dev"); + + __tty_fasync(-1, filp, 0); + + idx = tty->index; + pty_master = (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER); + devpts = (tty->driver->flags & TTY_DRIVER_DEVPTS_MEM) != 0; + o_tty = tty->link; + +#ifdef TTY_PARANOIA_CHECK + if (idx < 0 || idx >= tty->driver->num) { + printk(KERN_DEBUG "tty_release_dev: bad idx when trying to " + "free (%s)\n", tty->name); + tty_unlock(); + return 0; + } + if (!devpts) { + if (tty != tty->driver->ttys[idx]) { + tty_unlock(); + printk(KERN_DEBUG "tty_release_dev: driver.table[%d] not tty " + "for (%s)\n", idx, tty->name); + return 0; + } + if (tty->termios != tty->driver->termios[idx]) { + tty_unlock(); + printk(KERN_DEBUG "tty_release_dev: driver.termios[%d] not termios " + "for (%s)\n", + idx, tty->name); + return 0; + } + } +#endif + +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "tty_release_dev of %s (tty count=%d)...", + tty_name(tty, buf), tty->count); +#endif + +#ifdef TTY_PARANOIA_CHECK + if (tty->driver->other && + !(tty->driver->flags & TTY_DRIVER_DEVPTS_MEM)) { + if (o_tty != tty->driver->other->ttys[idx]) { + tty_unlock(); + printk(KERN_DEBUG "tty_release_dev: other->table[%d] " + "not o_tty for (%s)\n", + idx, tty->name); + return 0 ; + } + if (o_tty->termios != tty->driver->other->termios[idx]) { + tty_unlock(); + printk(KERN_DEBUG "tty_release_dev: other->termios[%d] " + "not o_termios for (%s)\n", + idx, tty->name); + return 0; + } + if (o_tty->link != tty) { + tty_unlock(); + printk(KERN_DEBUG "tty_release_dev: bad pty pointers\n"); + return 0; + } + } +#endif + if (tty->ops->close) + tty->ops->close(tty, filp); + + tty_unlock(); + /* + * Sanity check: if tty->count is going to zero, there shouldn't be + * any waiters on tty->read_wait or tty->write_wait. We test the + * wait queues and kick everyone out _before_ actually starting to + * close. This ensures that we won't block while releasing the tty + * structure. + * + * The test for the o_tty closing is necessary, since the master and + * slave sides may close in any order. If the slave side closes out + * first, its count will be one, since the master side holds an open. + * Thus this test wouldn't be triggered at the time the slave closes, + * so we do it now. + * + * Note that it's possible for the tty to be opened again while we're + * flushing out waiters. By recalculating the closing flags before + * each iteration we avoid any problems. + */ + while (1) { + /* Guard against races with tty->count changes elsewhere and + opens on /dev/tty */ + + mutex_lock(&tty_mutex); + tty_lock(); + tty_closing = tty->count <= 1; + o_tty_closing = o_tty && + (o_tty->count <= (pty_master ? 1 : 0)); + do_sleep = 0; + + if (tty_closing) { + if (waitqueue_active(&tty->read_wait)) { + wake_up_poll(&tty->read_wait, POLLIN); + do_sleep++; + } + if (waitqueue_active(&tty->write_wait)) { + wake_up_poll(&tty->write_wait, POLLOUT); + do_sleep++; + } + } + if (o_tty_closing) { + if (waitqueue_active(&o_tty->read_wait)) { + wake_up_poll(&o_tty->read_wait, POLLIN); + do_sleep++; + } + if (waitqueue_active(&o_tty->write_wait)) { + wake_up_poll(&o_tty->write_wait, POLLOUT); + do_sleep++; + } + } + if (!do_sleep) + break; + + printk(KERN_WARNING "tty_release_dev: %s: read/write wait queue " + "active!\n", tty_name(tty, buf)); + tty_unlock(); + mutex_unlock(&tty_mutex); + schedule(); + } + + /* + * The closing flags are now consistent with the open counts on + * both sides, and we've completed the last operation that could + * block, so it's safe to proceed with closing. + */ + if (pty_master) { + if (--o_tty->count < 0) { + printk(KERN_WARNING "tty_release_dev: bad pty slave count " + "(%d) for %s\n", + o_tty->count, tty_name(o_tty, buf)); + o_tty->count = 0; + } + } + if (--tty->count < 0) { + printk(KERN_WARNING "tty_release_dev: bad tty->count (%d) for %s\n", + tty->count, tty_name(tty, buf)); + tty->count = 0; + } + + /* + * We've decremented tty->count, so we need to remove this file + * descriptor off the tty->tty_files list; this serves two + * purposes: + * - check_tty_count sees the correct number of file descriptors + * associated with this tty. + * - do_tty_hangup no longer sees this file descriptor as + * something that needs to be handled for hangups. + */ + tty_del_file(filp); + + /* + * Perform some housekeeping before deciding whether to return. + * + * Set the TTY_CLOSING flag if this was the last open. In the + * case of a pty we may have to wait around for the other side + * to close, and TTY_CLOSING makes sure we can't be reopened. + */ + if (tty_closing) + set_bit(TTY_CLOSING, &tty->flags); + if (o_tty_closing) + set_bit(TTY_CLOSING, &o_tty->flags); + + /* + * If _either_ side is closing, make sure there aren't any + * processes that still think tty or o_tty is their controlling + * tty. + */ + if (tty_closing || o_tty_closing) { + read_lock(&tasklist_lock); + session_clear_tty(tty->session); + if (o_tty) + session_clear_tty(o_tty->session); + read_unlock(&tasklist_lock); + } + + mutex_unlock(&tty_mutex); + + /* check whether both sides are closing ... */ + if (!tty_closing || (o_tty && !o_tty_closing)) { + tty_unlock(); + return 0; + } + +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "freeing tty structure..."); +#endif + /* + * Ask the line discipline code to release its structures + */ + tty_ldisc_release(tty, o_tty); + /* + * The release_tty function takes care of the details of clearing + * the slots and preserving the termios structure. + */ + release_tty(tty, idx); + + /* Make this pty number available for reallocation */ + if (devpts) + devpts_kill_index(inode, idx); + tty_unlock(); + return 0; +} + +/** + * tty_open - open a tty device + * @inode: inode of device file + * @filp: file pointer to tty + * + * tty_open and tty_release keep up the tty count that contains the + * number of opens done on a tty. We cannot use the inode-count, as + * different inodes might point to the same tty. + * + * Open-counting is needed for pty masters, as well as for keeping + * track of serial lines: DTR is dropped when the last close happens. + * (This is not done solely through tty->count, now. - Ted 1/27/92) + * + * The termios state of a pty is reset on first open so that + * settings don't persist across reuse. + * + * Locking: tty_mutex protects tty, get_tty_driver and tty_init_dev work. + * tty->count should protect the rest. + * ->siglock protects ->signal/->sighand + */ + +static int tty_open(struct inode *inode, struct file *filp) +{ + struct tty_struct *tty = NULL; + int noctty, retval; + struct tty_driver *driver; + int index; + dev_t device = inode->i_rdev; + unsigned saved_flags = filp->f_flags; + + nonseekable_open(inode, filp); + +retry_open: + noctty = filp->f_flags & O_NOCTTY; + index = -1; + retval = 0; + + mutex_lock(&tty_mutex); + tty_lock(); + + if (device == MKDEV(TTYAUX_MAJOR, 0)) { + tty = get_current_tty(); + if (!tty) { + tty_unlock(); + mutex_unlock(&tty_mutex); + return -ENXIO; + } + driver = tty_driver_kref_get(tty->driver); + index = tty->index; + filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */ + /* noctty = 1; */ + /* FIXME: Should we take a driver reference ? */ + tty_kref_put(tty); + goto got_driver; + } +#ifdef CONFIG_VT + if (device == MKDEV(TTY_MAJOR, 0)) { + extern struct tty_driver *console_driver; + driver = tty_driver_kref_get(console_driver); + index = fg_console; + noctty = 1; + goto got_driver; + } +#endif + if (device == MKDEV(TTYAUX_MAJOR, 1)) { + struct tty_driver *console_driver = console_device(&index); + if (console_driver) { + driver = tty_driver_kref_get(console_driver); + if (driver) { + /* Don't let /dev/console block */ + filp->f_flags |= O_NONBLOCK; + noctty = 1; + goto got_driver; + } + } + tty_unlock(); + mutex_unlock(&tty_mutex); + return -ENODEV; + } + + driver = get_tty_driver(device, &index); + if (!driver) { + tty_unlock(); + mutex_unlock(&tty_mutex); + return -ENODEV; + } +got_driver: + if (!tty) { + /* check whether we're reopening an existing tty */ + tty = tty_driver_lookup_tty(driver, inode, index); + + if (IS_ERR(tty)) { + tty_unlock(); + mutex_unlock(&tty_mutex); + return PTR_ERR(tty); + } + } + + if (tty) { + retval = tty_reopen(tty); + if (retval) + tty = ERR_PTR(retval); + } else + tty = tty_init_dev(driver, index, 0); + + mutex_unlock(&tty_mutex); + tty_driver_kref_put(driver); + if (IS_ERR(tty)) { + tty_unlock(); + return PTR_ERR(tty); + } + + retval = tty_add_file(tty, filp); + if (retval) { + tty_unlock(); + return retval; + } + + check_tty_count(tty, "tty_open"); + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + noctty = 1; +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "opening %s...", tty->name); +#endif + if (!retval) { + if (tty->ops->open) + retval = tty->ops->open(tty, filp); + else + retval = -ENODEV; + } + filp->f_flags = saved_flags; + + if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && + !capable(CAP_SYS_ADMIN)) + retval = -EBUSY; + + if (retval) { +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "error %d in opening %s...", retval, + tty->name); +#endif + tty_unlock(); /* need to call tty_release without BTM */ + tty_release(inode, filp); + if (retval != -ERESTARTSYS) + return retval; + + if (signal_pending(current)) + return retval; + + schedule(); + /* + * Need to reset f_op in case a hangup happened. + */ + tty_lock(); + if (filp->f_op == &hung_up_tty_fops) + filp->f_op = &tty_fops; + tty_unlock(); + goto retry_open; + } + tty_unlock(); + + + mutex_lock(&tty_mutex); + tty_lock(); + spin_lock_irq(¤t->sighand->siglock); + if (!noctty && + current->signal->leader && + !current->signal->tty && + tty->session == NULL) + __proc_set_tty(current, tty); + spin_unlock_irq(¤t->sighand->siglock); + tty_unlock(); + mutex_unlock(&tty_mutex); + return 0; +} + + + +/** + * tty_poll - check tty status + * @filp: file being polled + * @wait: poll wait structures to update + * + * Call the line discipline polling method to obtain the poll + * status of the device. + * + * Locking: locks called line discipline but ldisc poll method + * may be re-entered freely by other callers. + */ + +static unsigned int tty_poll(struct file *filp, poll_table *wait) +{ + struct tty_struct *tty = file_tty(filp); + struct tty_ldisc *ld; + int ret = 0; + + if (tty_paranoia_check(tty, filp->f_path.dentry->d_inode, "tty_poll")) + return 0; + + ld = tty_ldisc_ref_wait(tty); + if (ld->ops->poll) + ret = (ld->ops->poll)(tty, filp, wait); + tty_ldisc_deref(ld); + return ret; +} + +static int __tty_fasync(int fd, struct file *filp, int on) +{ + struct tty_struct *tty = file_tty(filp); + unsigned long flags; + int retval = 0; + + if (tty_paranoia_check(tty, filp->f_path.dentry->d_inode, "tty_fasync")) + goto out; + + retval = fasync_helper(fd, filp, on, &tty->fasync); + if (retval <= 0) + goto out; + + if (on) { + enum pid_type type; + struct pid *pid; + if (!waitqueue_active(&tty->read_wait)) + tty->minimum_to_wake = 1; + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (tty->pgrp) { + pid = tty->pgrp; + type = PIDTYPE_PGID; + } else { + pid = task_pid(current); + type = PIDTYPE_PID; + } + get_pid(pid); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + retval = __f_setown(filp, pid, type, 0); + put_pid(pid); + if (retval) + goto out; + } else { + if (!tty->fasync && !waitqueue_active(&tty->read_wait)) + tty->minimum_to_wake = N_TTY_BUF_SIZE; + } + retval = 0; +out: + return retval; +} + +static int tty_fasync(int fd, struct file *filp, int on) +{ + int retval; + tty_lock(); + retval = __tty_fasync(fd, filp, on); + tty_unlock(); + return retval; +} + +/** + * tiocsti - fake input character + * @tty: tty to fake input into + * @p: pointer to character + * + * Fake input to a tty device. Does the necessary locking and + * input management. + * + * FIXME: does not honour flow control ?? + * + * Locking: + * Called functions take tty_ldisc_lock + * current->signal->tty check is safe without locks + * + * FIXME: may race normal receive processing + */ + +static int tiocsti(struct tty_struct *tty, char __user *p) +{ + char ch, mbz = 0; + struct tty_ldisc *ld; + + if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + if (get_user(ch, p)) + return -EFAULT; + tty_audit_tiocsti(tty, ch); + ld = tty_ldisc_ref_wait(tty); + ld->ops->receive_buf(tty, &ch, &mbz, 1); + tty_ldisc_deref(ld); + return 0; +} + +/** + * tiocgwinsz - implement window query ioctl + * @tty; tty + * @arg: user buffer for result + * + * Copies the kernel idea of the window size into the user buffer. + * + * Locking: tty->termios_mutex is taken to ensure the winsize data + * is consistent. + */ + +static int tiocgwinsz(struct tty_struct *tty, struct winsize __user *arg) +{ + int err; + + mutex_lock(&tty->termios_mutex); + err = copy_to_user(arg, &tty->winsize, sizeof(*arg)); + mutex_unlock(&tty->termios_mutex); + + return err ? -EFAULT: 0; +} + +/** + * tty_do_resize - resize event + * @tty: tty being resized + * @rows: rows (character) + * @cols: cols (character) + * + * Update the termios variables and send the necessary signals to + * peform a terminal resize correctly + */ + +int tty_do_resize(struct tty_struct *tty, struct winsize *ws) +{ + struct pid *pgrp; + unsigned long flags; + + /* Lock the tty */ + 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 */ + spin_lock_irqsave(&tty->ctrl_lock, flags); + pgrp = get_pid(tty->pgrp); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + + if (pgrp) + kill_pgrp(pgrp, SIGWINCH, 1); + put_pid(pgrp); + + tty->winsize = *ws; +done: + mutex_unlock(&tty->termios_mutex); + return 0; +} + +/** + * tiocswinsz - implement window size set ioctl + * @tty; tty side of tty + * @arg: user buffer for result + * + * Copies the user idea of the window size to the kernel. Traditionally + * this is just advisory information but for the Linux console it + * actually has driver level meaning and triggers a VC resize. + * + * Locking: + * Driver dependant. The default do_resize method takes the + * tty termios mutex and ctrl_lock. The console takes its own lock + * then calls into the default method. + */ + +static int tiocswinsz(struct tty_struct *tty, struct winsize __user *arg) +{ + struct winsize tmp_ws; + if (copy_from_user(&tmp_ws, arg, sizeof(*arg))) + return -EFAULT; + + if (tty->ops->resize) + return tty->ops->resize(tty, &tmp_ws); + else + return tty_do_resize(tty, &tmp_ws); +} + +/** + * tioccons - allow admin to move logical console + * @file: the file to become console + * + * Allow the adminstrator to move the redirected console device + * + * Locking: uses redirect_lock to guard the redirect information + */ + +static int tioccons(struct file *file) +{ + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (file->f_op->write == redirected_tty_write) { + struct file *f; + spin_lock(&redirect_lock); + f = redirect; + redirect = NULL; + spin_unlock(&redirect_lock); + if (f) + fput(f); + return 0; + } + spin_lock(&redirect_lock); + if (redirect) { + spin_unlock(&redirect_lock); + return -EBUSY; + } + get_file(file); + redirect = file; + spin_unlock(&redirect_lock); + return 0; +} + +/** + * fionbio - non blocking ioctl + * @file: file to set blocking value + * @p: user parameter + * + * Historical tty interfaces had a blocking control ioctl before + * the generic functionality existed. This piece of history is preserved + * in the expected tty API of posix OS's. + * + * Locking: none, the open file handle ensures it won't go away. + */ + +static int fionbio(struct file *file, int __user *p) +{ + int nonblock; + + if (get_user(nonblock, p)) + return -EFAULT; + + spin_lock(&file->f_lock); + if (nonblock) + file->f_flags |= O_NONBLOCK; + else + file->f_flags &= ~O_NONBLOCK; + spin_unlock(&file->f_lock); + return 0; +} + +/** + * tiocsctty - set controlling tty + * @tty: tty structure + * @arg: user argument + * + * This ioctl is used to manage job control. It permits a session + * leader to set this tty as the controlling tty for the session. + * + * Locking: + * Takes tty_mutex() to protect tty instance + * Takes tasklist_lock internally to walk sessions + * Takes ->siglock() when updating signal->tty + */ + +static int tiocsctty(struct tty_struct *tty, int arg) +{ + int ret = 0; + if (current->signal->leader && (task_session(current) == tty->session)) + return ret; + + mutex_lock(&tty_mutex); + /* + * The process must be a session leader and + * not have a controlling tty already. + */ + if (!current->signal->leader || current->signal->tty) { + ret = -EPERM; + goto unlock; + } + + if (tty->session) { + /* + * This tty is already the controlling + * tty for another session group! + */ + if (arg == 1 && capable(CAP_SYS_ADMIN)) { + /* + * Steal it away + */ + read_lock(&tasklist_lock); + session_clear_tty(tty->session); + read_unlock(&tasklist_lock); + } else { + ret = -EPERM; + goto unlock; + } + } + proc_set_tty(current, tty); +unlock: + mutex_unlock(&tty_mutex); + return ret; +} + +/** + * tty_get_pgrp - return a ref counted pgrp pid + * @tty: tty to read + * + * Returns a refcounted instance of the pid struct for the process + * group controlling the tty. + */ + +struct pid *tty_get_pgrp(struct tty_struct *tty) +{ + unsigned long flags; + struct pid *pgrp; + + spin_lock_irqsave(&tty->ctrl_lock, flags); + pgrp = get_pid(tty->pgrp); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + + return pgrp; +} +EXPORT_SYMBOL_GPL(tty_get_pgrp); + +/** + * tiocgpgrp - get process group + * @tty: tty passed by user + * @real_tty: tty side of the tty pased by the user if a pty else the tty + * @p: returned pid + * + * Obtain the process group of the tty. If there is no process group + * return an error. + * + * Locking: none. Reference to current->signal->tty is safe. + */ + +static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) +{ + struct pid *pid; + int ret; + /* + * (tty == real_tty) is a cheap way of + * testing if the tty is NOT a master pty. + */ + if (tty == real_tty && current->signal->tty != real_tty) + return -ENOTTY; + pid = tty_get_pgrp(real_tty); + ret = put_user(pid_vnr(pid), p); + put_pid(pid); + return ret; +} + +/** + * tiocspgrp - attempt to set process group + * @tty: tty passed by user + * @real_tty: tty side device matching tty passed by user + * @p: pid pointer + * + * Set the process group of the tty to the session passed. Only + * permitted where the tty session is our session. + * + * Locking: RCU, ctrl lock + */ + +static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) +{ + struct pid *pgrp; + pid_t pgrp_nr; + int retval = tty_check_change(real_tty); + unsigned long flags; + + if (retval == -EIO) + return -ENOTTY; + if (retval) + return retval; + if (!current->signal->tty || + (current->signal->tty != real_tty) || + (real_tty->session != task_session(current))) + return -ENOTTY; + if (get_user(pgrp_nr, p)) + return -EFAULT; + if (pgrp_nr < 0) + return -EINVAL; + rcu_read_lock(); + pgrp = find_vpid(pgrp_nr); + retval = -ESRCH; + if (!pgrp) + goto out_unlock; + retval = -EPERM; + if (session_of_pgrp(pgrp) != task_session(current)) + goto out_unlock; + retval = 0; + spin_lock_irqsave(&tty->ctrl_lock, flags); + put_pid(real_tty->pgrp); + real_tty->pgrp = get_pid(pgrp); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); +out_unlock: + rcu_read_unlock(); + return retval; +} + +/** + * tiocgsid - get session id + * @tty: tty passed by user + * @real_tty: tty side of the tty pased by the user if a pty else the tty + * @p: pointer to returned session id + * + * Obtain the session id of the tty. If there is no session + * return an error. + * + * Locking: none. Reference to current->signal->tty is safe. + */ + +static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) +{ + /* + * (tty == real_tty) is a cheap way of + * testing if the tty is NOT a master pty. + */ + if (tty == real_tty && current->signal->tty != real_tty) + return -ENOTTY; + if (!real_tty->session) + return -ENOTTY; + return put_user(pid_vnr(real_tty->session), p); +} + +/** + * tiocsetd - set line discipline + * @tty: tty device + * @p: pointer to user data + * + * Set the line discipline according to user request. + * + * Locking: see tty_set_ldisc, this function is just a helper + */ + +static int tiocsetd(struct tty_struct *tty, int __user *p) +{ + int ldisc; + int ret; + + if (get_user(ldisc, p)) + return -EFAULT; + + ret = tty_set_ldisc(tty, ldisc); + + return ret; +} + +/** + * send_break - performed time break + * @tty: device to break on + * @duration: timeout in mS + * + * Perform a timed break on hardware that lacks its own driver level + * timed break functionality. + * + * Locking: + * atomic_write_lock serializes + * + */ + +static int send_break(struct tty_struct *tty, unsigned int duration) +{ + int retval; + + if (tty->ops->break_ctl == NULL) + return 0; + + if (tty->driver->flags & TTY_DRIVER_HARDWARE_BREAK) + retval = tty->ops->break_ctl(tty, duration); + else { + /* Do the work ourselves */ + if (tty_write_lock(tty, 0) < 0) + return -EINTR; + retval = tty->ops->break_ctl(tty, -1); + if (retval) + goto out; + if (!signal_pending(current)) + msleep_interruptible(duration); + retval = tty->ops->break_ctl(tty, 0); +out: + tty_write_unlock(tty); + if (signal_pending(current)) + retval = -EINTR; + } + return retval; +} + +/** + * tty_tiocmget - get modem status + * @tty: tty device + * @file: user file pointer + * @p: pointer to result + * + * Obtain the modem status bits from the tty driver if the feature + * is supported. Return -EINVAL if it is not available. + * + * Locking: none (up to the driver) + */ + +static int tty_tiocmget(struct tty_struct *tty, struct file *file, int __user *p) +{ + int retval = -EINVAL; + + if (tty->ops->tiocmget) { + retval = tty->ops->tiocmget(tty, file); + + if (retval >= 0) + retval = put_user(retval, p); + } + return retval; +} + +/** + * tty_tiocmset - set modem status + * @tty: tty device + * @file: user file pointer + * @cmd: command - clear bits, set bits or set all + * @p: pointer to desired bits + * + * Set the modem status bits from the tty driver if the feature + * is supported. Return -EINVAL if it is not available. + * + * Locking: none (up to the driver) + */ + +static int tty_tiocmset(struct tty_struct *tty, struct file *file, unsigned int cmd, + unsigned __user *p) +{ + int retval; + unsigned int set, clear, val; + + if (tty->ops->tiocmset == NULL) + return -EINVAL; + + retval = get_user(val, p); + if (retval) + return retval; + set = clear = 0; + switch (cmd) { + case TIOCMBIS: + set = val; + break; + case TIOCMBIC: + clear = val; + break; + case TIOCMSET: + set = val; + clear = ~val; + break; + } + set &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP; + clear &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP; + return tty->ops->tiocmset(tty, file, set, clear); +} + +static int tty_tiocgicount(struct tty_struct *tty, void __user *arg) +{ + int retval = -EINVAL; + struct serial_icounter_struct icount; + memset(&icount, 0, sizeof(icount)); + if (tty->ops->get_icount) + retval = tty->ops->get_icount(tty, &icount); + if (retval != 0) + return retval; + if (copy_to_user(arg, &icount, sizeof(icount))) + return -EFAULT; + return 0; +} + +struct tty_struct *tty_pair_get_tty(struct tty_struct *tty) +{ + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + tty = tty->link; + return tty; +} +EXPORT_SYMBOL(tty_pair_get_tty); + +struct tty_struct *tty_pair_get_pty(struct tty_struct *tty) +{ + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + return tty; + return tty->link; +} +EXPORT_SYMBOL(tty_pair_get_pty); + +/* + * Split this up, as gcc can choke on it otherwise.. + */ +long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct tty_struct *tty = file_tty(file); + struct tty_struct *real_tty; + void __user *p = (void __user *)arg; + int retval; + struct tty_ldisc *ld; + struct inode *inode = file->f_dentry->d_inode; + + if (tty_paranoia_check(tty, inode, "tty_ioctl")) + return -EINVAL; + + real_tty = tty_pair_get_tty(tty); + + /* + * Factor out some common prep work + */ + switch (cmd) { + case TIOCSETD: + case TIOCSBRK: + case TIOCCBRK: + case TCSBRK: + case TCSBRKP: + retval = tty_check_change(tty); + if (retval) + return retval; + if (cmd != TIOCCBRK) { + tty_wait_until_sent(tty, 0); + if (signal_pending(current)) + return -EINTR; + } + break; + } + + /* + * Now do the stuff. + */ + switch (cmd) { + case TIOCSTI: + return tiocsti(tty, p); + case TIOCGWINSZ: + return tiocgwinsz(real_tty, p); + case TIOCSWINSZ: + return tiocswinsz(real_tty, p); + case TIOCCONS: + return real_tty != tty ? -EINVAL : tioccons(file); + case FIONBIO: + return fionbio(file, p); + case TIOCEXCL: + set_bit(TTY_EXCLUSIVE, &tty->flags); + return 0; + case TIOCNXCL: + clear_bit(TTY_EXCLUSIVE, &tty->flags); + return 0; + case TIOCNOTTY: + if (current->signal->tty != tty) + return -ENOTTY; + no_tty(); + return 0; + case TIOCSCTTY: + return tiocsctty(tty, arg); + case TIOCGPGRP: + return tiocgpgrp(tty, real_tty, p); + case TIOCSPGRP: + return tiocspgrp(tty, real_tty, p); + case TIOCGSID: + return tiocgsid(tty, real_tty, p); + case TIOCGETD: + return put_user(tty->ldisc->ops->num, (int __user *)p); + case TIOCSETD: + return tiocsetd(tty, p); + /* + * Break handling + */ + case TIOCSBRK: /* Turn break on, unconditionally */ + if (tty->ops->break_ctl) + return tty->ops->break_ctl(tty, -1); + return 0; + case TIOCCBRK: /* Turn break off, unconditionally */ + if (tty->ops->break_ctl) + return tty->ops->break_ctl(tty, 0); + return 0; + case TCSBRK: /* SVID version: non-zero arg --> no break */ + /* non-zero arg means wait for all output data + * to be sent (performed above) but don't send break. + * This is used by the tcdrain() termios function. + */ + if (!arg) + return send_break(tty, 250); + return 0; + case TCSBRKP: /* support for POSIX tcsendbreak() */ + return send_break(tty, arg ? arg*100 : 250); + + case TIOCMGET: + return tty_tiocmget(tty, file, p); + case TIOCMSET: + case TIOCMBIC: + case TIOCMBIS: + return tty_tiocmset(tty, file, cmd, p); + case TIOCGICOUNT: + retval = tty_tiocgicount(tty, p); + /* For the moment allow fall through to the old method */ + if (retval != -EINVAL) + return retval; + break; + case TCFLSH: + switch (arg) { + case TCIFLUSH: + case TCIOFLUSH: + /* flush tty buffer and allow ldisc to process ioctl */ + tty_buffer_flush(tty); + break; + } + break; + } + if (tty->ops->ioctl) { + retval = (tty->ops->ioctl)(tty, file, cmd, arg); + if (retval != -ENOIOCTLCMD) + return retval; + } + ld = tty_ldisc_ref_wait(tty); + retval = -EINVAL; + if (ld->ops->ioctl) { + retval = ld->ops->ioctl(tty, file, cmd, arg); + if (retval == -ENOIOCTLCMD) + retval = -EINVAL; + } + tty_ldisc_deref(ld); + return retval; +} + +#ifdef CONFIG_COMPAT +static long tty_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct inode *inode = file->f_dentry->d_inode; + struct tty_struct *tty = file_tty(file); + struct tty_ldisc *ld; + int retval = -ENOIOCTLCMD; + + if (tty_paranoia_check(tty, inode, "tty_ioctl")) + return -EINVAL; + + if (tty->ops->compat_ioctl) { + retval = (tty->ops->compat_ioctl)(tty, file, cmd, arg); + if (retval != -ENOIOCTLCMD) + return retval; + } + + ld = tty_ldisc_ref_wait(tty); + if (ld->ops->compat_ioctl) + retval = ld->ops->compat_ioctl(tty, file, cmd, arg); + tty_ldisc_deref(ld); + + return retval; +} +#endif + +/* + * This implements the "Secure Attention Key" --- the idea is to + * prevent trojan horses by killing all processes associated with this + * tty when the user hits the "Secure Attention Key". Required for + * super-paranoid applications --- see the Orange Book for more details. + * + * This code could be nicer; ideally it should send a HUP, wait a few + * seconds, then send a INT, and then a KILL signal. But you then + * have to coordinate with the init process, since all processes associated + * with the current tty must be dead before the new getty is allowed + * to spawn. + * + * Now, if it would be correct ;-/ The current code has a nasty hole - + * it doesn't catch files in flight. We may send the descriptor to ourselves + * via AF_UNIX socket, close it and later fetch from socket. FIXME. + * + * Nasty bug: do_SAK is being called in interrupt context. This can + * deadlock. We punt it up to process context. AKPM - 16Mar2001 + */ +void __do_SAK(struct tty_struct *tty) +{ +#ifdef TTY_SOFT_SAK + tty_hangup(tty); +#else + struct task_struct *g, *p; + struct pid *session; + int i; + struct file *filp; + struct fdtable *fdt; + + if (!tty) + return; + session = tty->session; + + tty_ldisc_flush(tty); + + tty_driver_flush_buffer(tty); + + read_lock(&tasklist_lock); + /* Kill the entire session */ + do_each_pid_task(session, PIDTYPE_SID, p) { + printk(KERN_NOTICE "SAK: killed process %d" + " (%s): task_session(p)==tty->session\n", + task_pid_nr(p), p->comm); + send_sig(SIGKILL, p, 1); + } while_each_pid_task(session, PIDTYPE_SID, p); + /* Now kill any processes that happen to have the + * tty open. + */ + do_each_thread(g, p) { + if (p->signal->tty == tty) { + printk(KERN_NOTICE "SAK: killed process %d" + " (%s): task_session(p)==tty->session\n", + task_pid_nr(p), p->comm); + send_sig(SIGKILL, p, 1); + continue; + } + task_lock(p); + if (p->files) { + /* + * We don't take a ref to the file, so we must + * hold ->file_lock instead. + */ + spin_lock(&p->files->file_lock); + fdt = files_fdtable(p->files); + for (i = 0; i < fdt->max_fds; i++) { + filp = fcheck_files(p->files, i); + if (!filp) + continue; + if (filp->f_op->read == tty_read && + file_tty(filp) == tty) { + printk(KERN_NOTICE "SAK: killed process %d" + " (%s): fd#%d opened to the tty\n", + task_pid_nr(p), p->comm, i); + force_sig(SIGKILL, p); + break; + } + } + spin_unlock(&p->files->file_lock); + } + task_unlock(p); + } while_each_thread(g, p); + read_unlock(&tasklist_lock); +#endif +} + +static void do_SAK_work(struct work_struct *work) +{ + struct tty_struct *tty = + container_of(work, struct tty_struct, SAK_work); + __do_SAK(tty); +} + +/* + * The tq handling here is a little racy - tty->SAK_work may already be queued. + * Fortunately we don't need to worry, because if ->SAK_work is already queued, + * the values which we write to it will be identical to the values which it + * already has. --akpm + */ +void do_SAK(struct tty_struct *tty) +{ + if (!tty) + return; + schedule_work(&tty->SAK_work); +} + +EXPORT_SYMBOL(do_SAK); + +static int dev_match_devt(struct device *dev, void *data) +{ + dev_t *devt = data; + return dev->devt == *devt; +} + +/* Must put_device() after it's unused! */ +static struct device *tty_get_device(struct tty_struct *tty) +{ + dev_t devt = tty_devnum(tty); + return class_find_device(tty_class, NULL, &devt, dev_match_devt); +} + + +/** + * initialize_tty_struct + * @tty: tty to initialize + * + * This subroutine initializes a tty structure that has been newly + * allocated. + * + * Locking: none - tty in question must not be exposed at this point + */ + +void initialize_tty_struct(struct tty_struct *tty, + struct tty_driver *driver, int idx) +{ + memset(tty, 0, sizeof(struct tty_struct)); + kref_init(&tty->kref); + tty->magic = TTY_MAGIC; + tty_ldisc_init(tty); + tty->session = NULL; + tty->pgrp = NULL; + tty->overrun_time = jiffies; + tty->buf.head = tty->buf.tail = NULL; + tty_buffer_init(tty); + mutex_init(&tty->termios_mutex); + mutex_init(&tty->ldisc_mutex); + init_waitqueue_head(&tty->write_wait); + init_waitqueue_head(&tty->read_wait); + INIT_WORK(&tty->hangup_work, do_tty_hangup); + mutex_init(&tty->atomic_read_lock); + mutex_init(&tty->atomic_write_lock); + mutex_init(&tty->output_lock); + mutex_init(&tty->echo_lock); + spin_lock_init(&tty->read_lock); + spin_lock_init(&tty->ctrl_lock); + INIT_LIST_HEAD(&tty->tty_files); + INIT_WORK(&tty->SAK_work, do_SAK_work); + + tty->driver = driver; + tty->ops = driver->ops; + tty->index = idx; + tty_line_name(driver, idx, tty->name); + tty->dev = tty_get_device(tty); +} + +/** + * tty_put_char - write one character to a tty + * @tty: tty + * @ch: character + * + * Write one byte to the tty using the provided put_char method + * if present. Returns the number of characters successfully output. + * + * Note: the specific put_char operation in the driver layer may go + * away soon. Don't call it directly, use this method + */ + +int tty_put_char(struct tty_struct *tty, unsigned char ch) +{ + if (tty->ops->put_char) + return tty->ops->put_char(tty, ch); + return tty->ops->write(tty, &ch, 1); +} +EXPORT_SYMBOL_GPL(tty_put_char); + +struct class *tty_class; + +/** + * tty_register_device - register a tty device + * @driver: the tty driver that describes the tty device + * @index: the index in the tty driver for this tty device + * @device: a struct device that is associated with this tty device. + * This field is optional, if there is no known struct device + * for this tty device it can be set to NULL safely. + * + * Returns a pointer to the struct device for this tty device + * (or ERR_PTR(-EFOO) on error). + * + * This call is required to be made to register an individual tty device + * if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set. If + * that bit is not set, this function should not be called by a tty + * driver. + * + * Locking: ?? + */ + +struct device *tty_register_device(struct tty_driver *driver, unsigned index, + struct device *device) +{ + char name[64]; + dev_t dev = MKDEV(driver->major, driver->minor_start) + index; + + if (index >= driver->num) { + printk(KERN_ERR "Attempt to register invalid tty line number " + " (%d).\n", index); + return ERR_PTR(-EINVAL); + } + + if (driver->type == TTY_DRIVER_TYPE_PTY) + pty_line_name(driver, index, name); + else + tty_line_name(driver, index, name); + + return device_create(tty_class, device, dev, NULL, name); +} +EXPORT_SYMBOL(tty_register_device); + +/** + * tty_unregister_device - unregister a tty device + * @driver: the tty driver that describes the tty device + * @index: the index in the tty driver for this tty device + * + * If a tty device is registered with a call to tty_register_device() then + * this function must be called when the tty device is gone. + * + * Locking: ?? + */ + +void tty_unregister_device(struct tty_driver *driver, unsigned index) +{ + device_destroy(tty_class, + MKDEV(driver->major, driver->minor_start) + index); +} +EXPORT_SYMBOL(tty_unregister_device); + +struct tty_driver *alloc_tty_driver(int lines) +{ + struct tty_driver *driver; + + driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL); + if (driver) { + kref_init(&driver->kref); + driver->magic = TTY_DRIVER_MAGIC; + driver->num = lines; + /* later we'll move allocation of tables here */ + } + return driver; +} +EXPORT_SYMBOL(alloc_tty_driver); + +static void destruct_tty_driver(struct kref *kref) +{ + struct tty_driver *driver = container_of(kref, struct tty_driver, kref); + int i; + struct ktermios *tp; + void *p; + + if (driver->flags & TTY_DRIVER_INSTALLED) { + /* + * Free the termios and termios_locked structures because + * we don't want to get memory leaks when modular tty + * drivers are removed from the kernel. + */ + for (i = 0; i < driver->num; i++) { + tp = driver->termios[i]; + if (tp) { + driver->termios[i] = NULL; + kfree(tp); + } + if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) + tty_unregister_device(driver, i); + } + p = driver->ttys; + proc_tty_unregister_driver(driver); + driver->ttys = NULL; + driver->termios = NULL; + kfree(p); + cdev_del(&driver->cdev); + } + kfree(driver); +} + +void tty_driver_kref_put(struct tty_driver *driver) +{ + kref_put(&driver->kref, destruct_tty_driver); +} +EXPORT_SYMBOL(tty_driver_kref_put); + +void tty_set_operations(struct tty_driver *driver, + const struct tty_operations *op) +{ + driver->ops = op; +}; +EXPORT_SYMBOL(tty_set_operations); + +void put_tty_driver(struct tty_driver *d) +{ + tty_driver_kref_put(d); +} +EXPORT_SYMBOL(put_tty_driver); + +/* + * Called by a tty driver to register itself. + */ +int tty_register_driver(struct tty_driver *driver) +{ + int error; + int i; + dev_t dev; + void **p = NULL; + struct device *d; + + if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) { + p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL); + if (!p) + return -ENOMEM; + } + + if (!driver->major) { + error = alloc_chrdev_region(&dev, driver->minor_start, + driver->num, driver->name); + if (!error) { + driver->major = MAJOR(dev); + driver->minor_start = MINOR(dev); + } + } else { + dev = MKDEV(driver->major, driver->minor_start); + error = register_chrdev_region(dev, driver->num, driver->name); + } + if (error < 0) { + kfree(p); + return error; + } + + if (p) { + driver->ttys = (struct tty_struct **)p; + driver->termios = (struct ktermios **)(p + driver->num); + } else { + driver->ttys = NULL; + driver->termios = NULL; + } + + cdev_init(&driver->cdev, &tty_fops); + driver->cdev.owner = driver->owner; + error = cdev_add(&driver->cdev, dev, driver->num); + if (error) { + unregister_chrdev_region(dev, driver->num); + driver->ttys = NULL; + driver->termios = NULL; + kfree(p); + return error; + } + + mutex_lock(&tty_mutex); + list_add(&driver->tty_drivers, &tty_drivers); + mutex_unlock(&tty_mutex); + + if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { + for (i = 0; i < driver->num; i++) { + d = tty_register_device(driver, i, NULL); + if (IS_ERR(d)) { + error = PTR_ERR(d); + goto err; + } + } + } + proc_tty_register_driver(driver); + driver->flags |= TTY_DRIVER_INSTALLED; + return 0; + +err: + for (i--; i >= 0; i--) + tty_unregister_device(driver, i); + + mutex_lock(&tty_mutex); + list_del(&driver->tty_drivers); + mutex_unlock(&tty_mutex); + + unregister_chrdev_region(dev, driver->num); + driver->ttys = NULL; + driver->termios = NULL; + kfree(p); + return error; +} + +EXPORT_SYMBOL(tty_register_driver); + +/* + * Called by a tty driver to unregister itself. + */ +int tty_unregister_driver(struct tty_driver *driver) +{ +#if 0 + /* FIXME */ + if (driver->refcount) + return -EBUSY; +#endif + unregister_chrdev_region(MKDEV(driver->major, driver->minor_start), + driver->num); + mutex_lock(&tty_mutex); + list_del(&driver->tty_drivers); + mutex_unlock(&tty_mutex); + return 0; +} + +EXPORT_SYMBOL(tty_unregister_driver); + +dev_t tty_devnum(struct tty_struct *tty) +{ + return MKDEV(tty->driver->major, tty->driver->minor_start) + tty->index; +} +EXPORT_SYMBOL(tty_devnum); + +void proc_clear_tty(struct task_struct *p) +{ + unsigned long flags; + struct tty_struct *tty; + spin_lock_irqsave(&p->sighand->siglock, flags); + tty = p->signal->tty; + p->signal->tty = NULL; + spin_unlock_irqrestore(&p->sighand->siglock, flags); + tty_kref_put(tty); +} + +/* Called under the sighand lock */ + +static void __proc_set_tty(struct task_struct *tsk, struct tty_struct *tty) +{ + if (tty) { + unsigned long flags; + /* We should not have a session or pgrp to put here but.... */ + spin_lock_irqsave(&tty->ctrl_lock, flags); + put_pid(tty->session); + put_pid(tty->pgrp); + tty->pgrp = get_pid(task_pgrp(tsk)); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + tty->session = get_pid(task_session(tsk)); + if (tsk->signal->tty) { + printk(KERN_DEBUG "tty not NULL!!\n"); + tty_kref_put(tsk->signal->tty); + } + } + put_pid(tsk->signal->tty_old_pgrp); + tsk->signal->tty = tty_kref_get(tty); + tsk->signal->tty_old_pgrp = NULL; +} + +static void proc_set_tty(struct task_struct *tsk, struct tty_struct *tty) +{ + spin_lock_irq(&tsk->sighand->siglock); + __proc_set_tty(tsk, tty); + spin_unlock_irq(&tsk->sighand->siglock); +} + +struct tty_struct *get_current_tty(void) +{ + struct tty_struct *tty; + unsigned long flags; + + spin_lock_irqsave(¤t->sighand->siglock, flags); + tty = tty_kref_get(current->signal->tty); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + return tty; +} +EXPORT_SYMBOL_GPL(get_current_tty); + +void tty_default_fops(struct file_operations *fops) +{ + *fops = tty_fops; +} + +/* + * Initialize the console device. This is called *early*, so + * we can't necessarily depend on lots of kernel help here. + * Just do some early initializations, and do the complex setup + * later. + */ +void __init console_init(void) +{ + initcall_t *call; + + /* Setup the default TTY line discipline. */ + tty_ldisc_begin(); + + /* + * set up the console device so that later boot sequences can + * inform about problems etc.. + */ + call = __con_initcall_start; + while (call < __con_initcall_end) { + (*call)(); + call++; + } +} + +static char *tty_devnode(struct device *dev, mode_t *mode) +{ + if (!mode) + return NULL; + if (dev->devt == MKDEV(TTYAUX_MAJOR, 0) || + dev->devt == MKDEV(TTYAUX_MAJOR, 2)) + *mode = 0666; + return NULL; +} + +static int __init tty_class_init(void) +{ + tty_class = class_create(THIS_MODULE, "tty"); + if (IS_ERR(tty_class)) + return PTR_ERR(tty_class); + tty_class->devnode = tty_devnode; + return 0; +} + +postcore_initcall(tty_class_init); + +/* 3/2004 jmc: why do these devices exist? */ + +static struct cdev tty_cdev, console_cdev; + +/* + * Ok, now we can initialize the rest of the tty devices and can count + * on memory allocations, interrupts etc.. + */ +int __init tty_init(void) +{ + cdev_init(&tty_cdev, &tty_fops); + if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) || + register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0) + panic("Couldn't register /dev/tty driver\n"); + device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, + "tty"); + + cdev_init(&console_cdev, &console_fops); + if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) || + register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0) + panic("Couldn't register /dev/console driver\n"); + device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL, + "console"); + +#ifdef CONFIG_VT + vty_init(&console_fops); +#endif + return 0; +} + diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c new file mode 100644 index 000000000000..0c1889971459 --- /dev/null +++ b/drivers/tty/tty_ioctl.c @@ -0,0 +1,1179 @@ +/* + * linux/drivers/char/tty_ioctl.c + * + * Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds + * + * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines + * which can be dynamically activated and de-activated by the line + * discipline handling modules (like SLIP). + */ + +#include <linux/types.h> +#include <linux/termios.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/tty.h> +#include <linux/fcntl.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/mutex.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#undef TTY_DEBUG_WAIT_UNTIL_SENT + +#undef DEBUG + +/* + * Internal flag options for termios setting behavior + */ +#define TERMIOS_FLUSH 1 +#define TERMIOS_WAIT 2 +#define TERMIOS_TERMIO 4 +#define TERMIOS_OLD 8 + + +/** + * tty_chars_in_buffer - characters pending + * @tty: terminal + * + * Return the number of bytes of data in the device private + * output queue. If no private method is supplied there is assumed + * to be no queue on the device. + */ + +int tty_chars_in_buffer(struct tty_struct *tty) +{ + if (tty->ops->chars_in_buffer) + return tty->ops->chars_in_buffer(tty); + else + return 0; +} +EXPORT_SYMBOL(tty_chars_in_buffer); + +/** + * tty_write_room - write queue space + * @tty: terminal + * + * Return the number of bytes that can be queued to this device + * at the present time. The result should be treated as a guarantee + * and the driver cannot offer a value it later shrinks by more than + * the number of bytes written. If no method is provided 2K is always + * returned and data may be lost as there will be no flow control. + */ + +int tty_write_room(struct tty_struct *tty) +{ + if (tty->ops->write_room) + return tty->ops->write_room(tty); + return 2048; +} +EXPORT_SYMBOL(tty_write_room); + +/** + * tty_driver_flush_buffer - discard internal buffer + * @tty: terminal + * + * Discard the internal output buffer for this device. If no method + * is provided then either the buffer cannot be hardware flushed or + * there is no buffer driver side. + */ +void tty_driver_flush_buffer(struct tty_struct *tty) +{ + if (tty->ops->flush_buffer) + tty->ops->flush_buffer(tty); +} +EXPORT_SYMBOL(tty_driver_flush_buffer); + +/** + * tty_throttle - flow control + * @tty: terminal + * + * Indicate that a tty should stop transmitting data down the stack. + * Takes the termios mutex to protect against parallel throttle/unthrottle + * and also to ensure the driver can consistently reference its own + * termios data at this point when implementing software flow control. + */ + +void tty_throttle(struct tty_struct *tty) +{ + mutex_lock(&tty->termios_mutex); + /* check TTY_THROTTLED first so it indicates our state */ + if (!test_and_set_bit(TTY_THROTTLED, &tty->flags) && + tty->ops->throttle) + tty->ops->throttle(tty); + mutex_unlock(&tty->termios_mutex); +} +EXPORT_SYMBOL(tty_throttle); + +/** + * tty_unthrottle - flow control + * @tty: terminal + * + * Indicate that a tty may continue transmitting data down the stack. + * Takes the termios mutex to protect against parallel throttle/unthrottle + * and also to ensure the driver can consistently reference its own + * termios data at this point when implementing software flow control. + * + * Drivers should however remember that the stack can issue a throttle, + * then change flow control method, then unthrottle. + */ + +void tty_unthrottle(struct tty_struct *tty) +{ + mutex_lock(&tty->termios_mutex); + if (test_and_clear_bit(TTY_THROTTLED, &tty->flags) && + tty->ops->unthrottle) + tty->ops->unthrottle(tty); + mutex_unlock(&tty->termios_mutex); +} +EXPORT_SYMBOL(tty_unthrottle); + +/** + * tty_wait_until_sent - wait for I/O to finish + * @tty: tty we are waiting for + * @timeout: how long we will wait + * + * Wait for characters pending in a tty driver to hit the wire, or + * for a timeout to occur (eg due to flow control) + * + * Locking: none + */ + +void tty_wait_until_sent(struct tty_struct *tty, long timeout) +{ +#ifdef TTY_DEBUG_WAIT_UNTIL_SENT + char buf[64]; + + printk(KERN_DEBUG "%s wait until sent...\n", tty_name(tty, buf)); +#endif + if (!timeout) + timeout = MAX_SCHEDULE_TIMEOUT; + if (wait_event_interruptible_timeout(tty->write_wait, + !tty_chars_in_buffer(tty), timeout) >= 0) { + if (tty->ops->wait_until_sent) + tty->ops->wait_until_sent(tty, timeout); + } +} +EXPORT_SYMBOL(tty_wait_until_sent); + + +/* + * Termios Helper Methods + */ + +static void unset_locked_termios(struct ktermios *termios, + struct ktermios *old, + struct ktermios *locked) +{ + int i; + +#define NOSET_MASK(x, y, z) (x = ((x) & ~(z)) | ((y) & (z))) + + if (!locked) { + printk(KERN_WARNING "Warning?!? termios_locked is NULL.\n"); + return; + } + + NOSET_MASK(termios->c_iflag, old->c_iflag, locked->c_iflag); + NOSET_MASK(termios->c_oflag, old->c_oflag, locked->c_oflag); + NOSET_MASK(termios->c_cflag, old->c_cflag, locked->c_cflag); + NOSET_MASK(termios->c_lflag, old->c_lflag, locked->c_lflag); + termios->c_line = locked->c_line ? old->c_line : termios->c_line; + for (i = 0; i < NCCS; i++) + termios->c_cc[i] = locked->c_cc[i] ? + old->c_cc[i] : termios->c_cc[i]; + /* FIXME: What should we do for i/ospeed */ +} + +/* + * Routine which returns the baud rate of the tty + * + * Note that the baud_table needs to be kept in sync with the + * include/asm/termbits.h file. + */ +static const speed_t baud_table[] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200, 230400, 460800, +#ifdef __sparc__ + 76800, 153600, 307200, 614400, 921600 +#else + 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, + 2500000, 3000000, 3500000, 4000000 +#endif +}; + +#ifndef __sparc__ +static const tcflag_t baud_bits[] = { + B0, B50, B75, B110, B134, B150, B200, B300, B600, + B1200, B1800, B2400, B4800, B9600, B19200, B38400, + B57600, B115200, B230400, B460800, B500000, B576000, + B921600, B1000000, B1152000, B1500000, B2000000, B2500000, + B3000000, B3500000, B4000000 +}; +#else +static const tcflag_t baud_bits[] = { + B0, B50, B75, B110, B134, B150, B200, B300, B600, + B1200, B1800, B2400, B4800, B9600, B19200, B38400, + B57600, B115200, B230400, B460800, B76800, B153600, + B307200, B614400, B921600 +}; +#endif + +static int n_baud_table = ARRAY_SIZE(baud_table); + +/** + * tty_termios_baud_rate + * @termios: termios structure + * + * Convert termios baud rate data into a speed. This should be called + * with the termios lock held if this termios is a terminal termios + * structure. May change the termios data. Device drivers can call this + * function but should use ->c_[io]speed directly as they are updated. + * + * Locking: none + */ + +speed_t tty_termios_baud_rate(struct ktermios *termios) +{ + unsigned int cbaud; + + cbaud = termios->c_cflag & CBAUD; + +#ifdef BOTHER + /* Magic token for arbitary speed via c_ispeed/c_ospeed */ + if (cbaud == BOTHER) + return termios->c_ospeed; +#endif + if (cbaud & CBAUDEX) { + cbaud &= ~CBAUDEX; + + if (cbaud < 1 || cbaud + 15 > n_baud_table) + termios->c_cflag &= ~CBAUDEX; + else + cbaud += 15; + } + return baud_table[cbaud]; +} +EXPORT_SYMBOL(tty_termios_baud_rate); + +/** + * tty_termios_input_baud_rate + * @termios: termios structure + * + * Convert termios baud rate data into a speed. This should be called + * with the termios lock held if this termios is a terminal termios + * structure. May change the termios data. Device drivers can call this + * function but should use ->c_[io]speed directly as they are updated. + * + * Locking: none + */ + +speed_t tty_termios_input_baud_rate(struct ktermios *termios) +{ +#ifdef IBSHIFT + unsigned int cbaud = (termios->c_cflag >> IBSHIFT) & CBAUD; + + if (cbaud == B0) + return tty_termios_baud_rate(termios); + + /* Magic token for arbitary speed via c_ispeed*/ + if (cbaud == BOTHER) + return termios->c_ispeed; + + if (cbaud & CBAUDEX) { + cbaud &= ~CBAUDEX; + + if (cbaud < 1 || cbaud + 15 > n_baud_table) + termios->c_cflag &= ~(CBAUDEX << IBSHIFT); + else + cbaud += 15; + } + return baud_table[cbaud]; +#else + return tty_termios_baud_rate(termios); +#endif +} +EXPORT_SYMBOL(tty_termios_input_baud_rate); + +/** + * tty_termios_encode_baud_rate + * @termios: ktermios structure holding user requested state + * @ispeed: input speed + * @ospeed: output speed + * + * Encode the speeds set into the passed termios structure. This is + * used as a library helper for drivers os that they can report back + * the actual speed selected when it differs from the speed requested + * + * For maximal back compatibility with legacy SYS5/POSIX *nix behaviour + * we need to carefully set the bits when the user does not get the + * desired speed. We allow small margins and preserve as much of possible + * of the input intent to keep compatibility. + * + * Locking: Caller should hold termios lock. This is already held + * when calling this function from the driver termios handler. + * + * The ifdefs deal with platforms whose owners have yet to update them + * and will all go away once this is done. + */ + +void tty_termios_encode_baud_rate(struct ktermios *termios, + speed_t ibaud, speed_t obaud) +{ + int i = 0; + int ifound = -1, ofound = -1; + int iclose = ibaud/50, oclose = obaud/50; + int ibinput = 0; + + if (obaud == 0) /* CD dropped */ + ibaud = 0; /* Clear ibaud to be sure */ + + termios->c_ispeed = ibaud; + termios->c_ospeed = obaud; + +#ifdef BOTHER + /* If the user asked for a precise weird speed give a precise weird + answer. If they asked for a Bfoo speed they many have problems + digesting non-exact replies so fuzz a bit */ + + if ((termios->c_cflag & CBAUD) == BOTHER) + oclose = 0; + if (((termios->c_cflag >> IBSHIFT) & CBAUD) == BOTHER) + iclose = 0; + if ((termios->c_cflag >> IBSHIFT) & CBAUD) + ibinput = 1; /* An input speed was specified */ +#endif + termios->c_cflag &= ~CBAUD; + + /* + * Our goal is to find a close match to the standard baud rate + * returned. Walk the baud rate table and if we get a very close + * match then report back the speed as a POSIX Bxxxx value by + * preference + */ + + do { + if (obaud - oclose <= baud_table[i] && + obaud + oclose >= baud_table[i]) { + termios->c_cflag |= baud_bits[i]; + ofound = i; + } + if (ibaud - iclose <= baud_table[i] && + ibaud + iclose >= baud_table[i]) { + /* For the case input == output don't set IBAUD bits + if the user didn't do so */ + if (ofound == i && !ibinput) + ifound = i; +#ifdef IBSHIFT + else { + ifound = i; + termios->c_cflag |= (baud_bits[i] << IBSHIFT); + } +#endif + } + } while (++i < n_baud_table); + + /* + * If we found no match then use BOTHER if provided or warn + * the user their platform maintainer needs to wake up if not. + */ +#ifdef BOTHER + if (ofound == -1) + termios->c_cflag |= BOTHER; + /* Set exact input bits only if the input and output differ or the + user already did */ + if (ifound == -1 && (ibaud != obaud || ibinput)) + termios->c_cflag |= (BOTHER << IBSHIFT); +#else + if (ifound == -1 || ofound == -1) { + printk_once(KERN_WARNING "tty: Unable to return correct " + "speed data as your architecture needs updating.\n"); + } +#endif +} +EXPORT_SYMBOL_GPL(tty_termios_encode_baud_rate); + +/** + * tty_encode_baud_rate - set baud rate of the tty + * @ibaud: input baud rate + * @obad: output baud rate + * + * Update the current termios data for the tty with the new speed + * settings. The caller must hold the termios_mutex for the tty in + * question. + */ + +void tty_encode_baud_rate(struct tty_struct *tty, speed_t ibaud, speed_t obaud) +{ + tty_termios_encode_baud_rate(tty->termios, ibaud, obaud); +} +EXPORT_SYMBOL_GPL(tty_encode_baud_rate); + +/** + * tty_get_baud_rate - get tty bit rates + * @tty: tty to query + * + * Returns the baud rate as an integer for this terminal. The + * termios lock must be held by the caller and the terminal bit + * flags may be updated. + * + * Locking: none + */ + +speed_t tty_get_baud_rate(struct tty_struct *tty) +{ + speed_t baud = tty_termios_baud_rate(tty->termios); + + if (baud == 38400 && tty->alt_speed) { + if (!tty->warned) { + printk(KERN_WARNING "Use of setserial/setrocket to " + "set SPD_* flags is deprecated\n"); + tty->warned = 1; + } + baud = tty->alt_speed; + } + + return baud; +} +EXPORT_SYMBOL(tty_get_baud_rate); + +/** + * tty_termios_copy_hw - copy hardware settings + * @new: New termios + * @old: Old termios + * + * Propogate the hardware specific terminal setting bits from + * the old termios structure to the new one. This is used in cases + * where the hardware does not support reconfiguration or as a helper + * in some cases where only minimal reconfiguration is supported + */ + +void tty_termios_copy_hw(struct ktermios *new, struct ktermios *old) +{ + /* The bits a dumb device handles in software. Smart devices need + to always provide a set_termios method */ + new->c_cflag &= HUPCL | CREAD | CLOCAL; + new->c_cflag |= old->c_cflag & ~(HUPCL | CREAD | CLOCAL); + new->c_ispeed = old->c_ispeed; + new->c_ospeed = old->c_ospeed; +} +EXPORT_SYMBOL(tty_termios_copy_hw); + +/** + * tty_termios_hw_change - check for setting change + * @a: termios + * @b: termios to compare + * + * Check if any of the bits that affect a dumb device have changed + * between the two termios structures, or a speed change is needed. + */ + +int tty_termios_hw_change(struct ktermios *a, struct ktermios *b) +{ + if (a->c_ispeed != b->c_ispeed || a->c_ospeed != b->c_ospeed) + return 1; + if ((a->c_cflag ^ b->c_cflag) & ~(HUPCL | CREAD | CLOCAL)) + return 1; + return 0; +} +EXPORT_SYMBOL(tty_termios_hw_change); + +/** + * change_termios - update termios values + * @tty: tty to update + * @new_termios: desired new value + * + * Perform updates to the termios values set on this terminal. There + * is a bit of layering violation here with n_tty in terms of the + * internal knowledge of this function. + * + * Locking: termios_mutex + */ + +static void change_termios(struct tty_struct *tty, struct ktermios *new_termios) +{ + struct ktermios old_termios; + struct tty_ldisc *ld; + unsigned long flags; + + /* + * Perform the actual termios internal changes under lock. + */ + + + /* FIXME: we need to decide on some locking/ordering semantics + for the set_termios notification eventually */ + mutex_lock(&tty->termios_mutex); + old_termios = *tty->termios; + *tty->termios = *new_termios; + unset_locked_termios(tty->termios, &old_termios, tty->termios_locked); + + /* See if packet mode change of state. */ + if (tty->link && tty->link->packet) { + int extproc = (old_termios.c_lflag & EXTPROC) | + (tty->termios->c_lflag & EXTPROC); + int old_flow = ((old_termios.c_iflag & IXON) && + (old_termios.c_cc[VSTOP] == '\023') && + (old_termios.c_cc[VSTART] == '\021')); + int new_flow = (I_IXON(tty) && + STOP_CHAR(tty) == '\023' && + START_CHAR(tty) == '\021'); + if ((old_flow != new_flow) || extproc) { + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (old_flow != new_flow) { + tty->ctrl_status &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP); + if (new_flow) + tty->ctrl_status |= TIOCPKT_DOSTOP; + else + tty->ctrl_status |= TIOCPKT_NOSTOP; + } + if (extproc) + tty->ctrl_status |= TIOCPKT_IOCTL; + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + wake_up_interruptible(&tty->link->read_wait); + } + } + + if (tty->ops->set_termios) + (*tty->ops->set_termios)(tty, &old_termios); + else + tty_termios_copy_hw(tty->termios, &old_termios); + + ld = tty_ldisc_ref(tty); + if (ld != NULL) { + if (ld->ops->set_termios) + (ld->ops->set_termios)(tty, &old_termios); + tty_ldisc_deref(ld); + } + mutex_unlock(&tty->termios_mutex); +} + +/** + * set_termios - set termios values for a tty + * @tty: terminal device + * @arg: user data + * @opt: option information + * + * Helper function to prepare termios data and run necessary other + * functions before using change_termios to do the actual changes. + * + * Locking: + * Called functions take ldisc and termios_mutex locks + */ + +static int set_termios(struct tty_struct *tty, void __user *arg, int opt) +{ + struct ktermios tmp_termios; + struct tty_ldisc *ld; + int retval = tty_check_change(tty); + + if (retval) + return retval; + + mutex_lock(&tty->termios_mutex); + memcpy(&tmp_termios, tty->termios, sizeof(struct ktermios)); + mutex_unlock(&tty->termios_mutex); + + if (opt & TERMIOS_TERMIO) { + if (user_termio_to_kernel_termios(&tmp_termios, + (struct termio __user *)arg)) + return -EFAULT; +#ifdef TCGETS2 + } else if (opt & TERMIOS_OLD) { + if (user_termios_to_kernel_termios_1(&tmp_termios, + (struct termios __user *)arg)) + return -EFAULT; + } else { + if (user_termios_to_kernel_termios(&tmp_termios, + (struct termios2 __user *)arg)) + return -EFAULT; + } +#else + } else if (user_termios_to_kernel_termios(&tmp_termios, + (struct termios __user *)arg)) + return -EFAULT; +#endif + + /* If old style Bfoo values are used then load c_ispeed/c_ospeed + * with the real speed so its unconditionally usable */ + tmp_termios.c_ispeed = tty_termios_input_baud_rate(&tmp_termios); + tmp_termios.c_ospeed = tty_termios_baud_rate(&tmp_termios); + + ld = tty_ldisc_ref(tty); + + if (ld != NULL) { + if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer) + ld->ops->flush_buffer(tty); + tty_ldisc_deref(ld); + } + + if (opt & TERMIOS_WAIT) { + tty_wait_until_sent(tty, 0); + if (signal_pending(current)) + return -EINTR; + } + + change_termios(tty, &tmp_termios); + + /* FIXME: Arguably if tmp_termios == tty->termios AND the + actual requested termios was not tmp_termios then we may + want to return an error as no user requested change has + succeeded */ + return 0; +} + +static void copy_termios(struct tty_struct *tty, struct ktermios *kterm) +{ + mutex_lock(&tty->termios_mutex); + memcpy(kterm, tty->termios, sizeof(struct ktermios)); + mutex_unlock(&tty->termios_mutex); +} + +static void copy_termios_locked(struct tty_struct *tty, struct ktermios *kterm) +{ + mutex_lock(&tty->termios_mutex); + memcpy(kterm, tty->termios_locked, sizeof(struct ktermios)); + mutex_unlock(&tty->termios_mutex); +} + +static int get_termio(struct tty_struct *tty, struct termio __user *termio) +{ + struct ktermios kterm; + copy_termios(tty, &kterm); + if (kernel_termios_to_user_termio(termio, &kterm)) + return -EFAULT; + return 0; +} + + +#ifdef TCGETX + +/** + * set_termiox - set termiox fields if possible + * @tty: terminal + * @arg: termiox structure from user + * @opt: option flags for ioctl type + * + * Implement the device calling points for the SYS5 termiox ioctl + * interface in Linux + */ + +static int set_termiox(struct tty_struct *tty, void __user *arg, int opt) +{ + struct termiox tnew; + struct tty_ldisc *ld; + + if (tty->termiox == NULL) + return -EINVAL; + if (copy_from_user(&tnew, arg, sizeof(struct termiox))) + return -EFAULT; + + ld = tty_ldisc_ref(tty); + if (ld != NULL) { + if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer) + ld->ops->flush_buffer(tty); + tty_ldisc_deref(ld); + } + if (opt & TERMIOS_WAIT) { + tty_wait_until_sent(tty, 0); + if (signal_pending(current)) + return -EINTR; + } + + mutex_lock(&tty->termios_mutex); + if (tty->ops->set_termiox) + tty->ops->set_termiox(tty, &tnew); + mutex_unlock(&tty->termios_mutex); + return 0; +} + +#endif + + +#ifdef TIOCGETP +/* + * These are deprecated, but there is limited support.. + * + * The "sg_flags" translation is a joke.. + */ +static int get_sgflags(struct tty_struct *tty) +{ + int flags = 0; + + if (!(tty->termios->c_lflag & ICANON)) { + if (tty->termios->c_lflag & ISIG) + flags |= 0x02; /* cbreak */ + else + flags |= 0x20; /* raw */ + } + if (tty->termios->c_lflag & ECHO) + flags |= 0x08; /* echo */ + if (tty->termios->c_oflag & OPOST) + if (tty->termios->c_oflag & ONLCR) + flags |= 0x10; /* crmod */ + return flags; +} + +static int get_sgttyb(struct tty_struct *tty, struct sgttyb __user *sgttyb) +{ + struct sgttyb tmp; + + mutex_lock(&tty->termios_mutex); + tmp.sg_ispeed = tty->termios->c_ispeed; + tmp.sg_ospeed = tty->termios->c_ospeed; + tmp.sg_erase = tty->termios->c_cc[VERASE]; + tmp.sg_kill = tty->termios->c_cc[VKILL]; + tmp.sg_flags = get_sgflags(tty); + mutex_unlock(&tty->termios_mutex); + + return copy_to_user(sgttyb, &tmp, sizeof(tmp)) ? -EFAULT : 0; +} + +static void set_sgflags(struct ktermios *termios, int flags) +{ + termios->c_iflag = ICRNL | IXON; + termios->c_oflag = 0; + termios->c_lflag = ISIG | ICANON; + if (flags & 0x02) { /* cbreak */ + termios->c_iflag = 0; + termios->c_lflag &= ~ICANON; + } + if (flags & 0x08) { /* echo */ + termios->c_lflag |= ECHO | ECHOE | ECHOK | + ECHOCTL | ECHOKE | IEXTEN; + } + if (flags & 0x10) { /* crmod */ + termios->c_oflag |= OPOST | ONLCR; + } + if (flags & 0x20) { /* raw */ + termios->c_iflag = 0; + termios->c_lflag &= ~(ISIG | ICANON); + } + if (!(termios->c_lflag & ICANON)) { + termios->c_cc[VMIN] = 1; + termios->c_cc[VTIME] = 0; + } +} + +/** + * set_sgttyb - set legacy terminal values + * @tty: tty structure + * @sgttyb: pointer to old style terminal structure + * + * Updates a terminal from the legacy BSD style terminal information + * structure. + * + * Locking: termios_mutex + */ + +static int set_sgttyb(struct tty_struct *tty, struct sgttyb __user *sgttyb) +{ + int retval; + struct sgttyb tmp; + struct ktermios termios; + + retval = tty_check_change(tty); + if (retval) + return retval; + + if (copy_from_user(&tmp, sgttyb, sizeof(tmp))) + return -EFAULT; + + mutex_lock(&tty->termios_mutex); + termios = *tty->termios; + termios.c_cc[VERASE] = tmp.sg_erase; + termios.c_cc[VKILL] = tmp.sg_kill; + set_sgflags(&termios, tmp.sg_flags); + /* Try and encode into Bfoo format */ +#ifdef BOTHER + tty_termios_encode_baud_rate(&termios, termios.c_ispeed, + termios.c_ospeed); +#endif + mutex_unlock(&tty->termios_mutex); + change_termios(tty, &termios); + return 0; +} +#endif + +#ifdef TIOCGETC +static int get_tchars(struct tty_struct *tty, struct tchars __user *tchars) +{ + struct tchars tmp; + + mutex_lock(&tty->termios_mutex); + tmp.t_intrc = tty->termios->c_cc[VINTR]; + tmp.t_quitc = tty->termios->c_cc[VQUIT]; + tmp.t_startc = tty->termios->c_cc[VSTART]; + tmp.t_stopc = tty->termios->c_cc[VSTOP]; + tmp.t_eofc = tty->termios->c_cc[VEOF]; + tmp.t_brkc = tty->termios->c_cc[VEOL2]; /* what is brkc anyway? */ + mutex_unlock(&tty->termios_mutex); + return copy_to_user(tchars, &tmp, sizeof(tmp)) ? -EFAULT : 0; +} + +static int set_tchars(struct tty_struct *tty, struct tchars __user *tchars) +{ + struct tchars tmp; + + if (copy_from_user(&tmp, tchars, sizeof(tmp))) + return -EFAULT; + mutex_lock(&tty->termios_mutex); + tty->termios->c_cc[VINTR] = tmp.t_intrc; + tty->termios->c_cc[VQUIT] = tmp.t_quitc; + tty->termios->c_cc[VSTART] = tmp.t_startc; + tty->termios->c_cc[VSTOP] = tmp.t_stopc; + tty->termios->c_cc[VEOF] = tmp.t_eofc; + tty->termios->c_cc[VEOL2] = tmp.t_brkc; /* what is brkc anyway? */ + mutex_unlock(&tty->termios_mutex); + return 0; +} +#endif + +#ifdef TIOCGLTC +static int get_ltchars(struct tty_struct *tty, struct ltchars __user *ltchars) +{ + struct ltchars tmp; + + mutex_lock(&tty->termios_mutex); + tmp.t_suspc = tty->termios->c_cc[VSUSP]; + /* what is dsuspc anyway? */ + tmp.t_dsuspc = tty->termios->c_cc[VSUSP]; + tmp.t_rprntc = tty->termios->c_cc[VREPRINT]; + /* what is flushc anyway? */ + tmp.t_flushc = tty->termios->c_cc[VEOL2]; + tmp.t_werasc = tty->termios->c_cc[VWERASE]; + tmp.t_lnextc = tty->termios->c_cc[VLNEXT]; + mutex_unlock(&tty->termios_mutex); + return copy_to_user(ltchars, &tmp, sizeof(tmp)) ? -EFAULT : 0; +} + +static int set_ltchars(struct tty_struct *tty, struct ltchars __user *ltchars) +{ + struct ltchars tmp; + + if (copy_from_user(&tmp, ltchars, sizeof(tmp))) + return -EFAULT; + + mutex_lock(&tty->termios_mutex); + tty->termios->c_cc[VSUSP] = tmp.t_suspc; + /* what is dsuspc anyway? */ + tty->termios->c_cc[VEOL2] = tmp.t_dsuspc; + tty->termios->c_cc[VREPRINT] = tmp.t_rprntc; + /* what is flushc anyway? */ + tty->termios->c_cc[VEOL2] = tmp.t_flushc; + tty->termios->c_cc[VWERASE] = tmp.t_werasc; + tty->termios->c_cc[VLNEXT] = tmp.t_lnextc; + mutex_unlock(&tty->termios_mutex); + return 0; +} +#endif + +/** + * send_prio_char - send priority character + * + * Send a high priority character to the tty even if stopped + * + * Locking: none for xchar method, write ordering for write method. + */ + +static int send_prio_char(struct tty_struct *tty, char ch) +{ + int was_stopped = tty->stopped; + + if (tty->ops->send_xchar) { + tty->ops->send_xchar(tty, ch); + return 0; + } + + if (tty_write_lock(tty, 0) < 0) + return -ERESTARTSYS; + + if (was_stopped) + start_tty(tty); + tty->ops->write(tty, &ch, 1); + if (was_stopped) + stop_tty(tty); + tty_write_unlock(tty); + return 0; +} + +/** + * tty_change_softcar - carrier change ioctl helper + * @tty: tty to update + * @arg: enable/disable CLOCAL + * + * Perform a change to the CLOCAL state and call into the driver + * layer to make it visible. All done with the termios mutex + */ + +static int tty_change_softcar(struct tty_struct *tty, int arg) +{ + int ret = 0; + int bit = arg ? CLOCAL : 0; + struct ktermios old; + + mutex_lock(&tty->termios_mutex); + old = *tty->termios; + tty->termios->c_cflag &= ~CLOCAL; + tty->termios->c_cflag |= bit; + if (tty->ops->set_termios) + tty->ops->set_termios(tty, &old); + if ((tty->termios->c_cflag & CLOCAL) != bit) + ret = -EINVAL; + mutex_unlock(&tty->termios_mutex); + return ret; +} + +/** + * tty_mode_ioctl - mode related ioctls + * @tty: tty for the ioctl + * @file: file pointer for the tty + * @cmd: command + * @arg: ioctl argument + * + * Perform non line discipline specific mode control ioctls. This + * is designed to be called by line disciplines to ensure they provide + * consistent mode setting. + */ + +int tty_mode_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct tty_struct *real_tty; + void __user *p = (void __user *)arg; + int ret = 0; + struct ktermios kterm; + + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + real_tty = tty->link; + else + real_tty = tty; + + switch (cmd) { +#ifdef TIOCGETP + case TIOCGETP: + return get_sgttyb(real_tty, (struct sgttyb __user *) arg); + case TIOCSETP: + case TIOCSETN: + return set_sgttyb(real_tty, (struct sgttyb __user *) arg); +#endif +#ifdef TIOCGETC + case TIOCGETC: + return get_tchars(real_tty, p); + case TIOCSETC: + return set_tchars(real_tty, p); +#endif +#ifdef TIOCGLTC + case TIOCGLTC: + return get_ltchars(real_tty, p); + case TIOCSLTC: + return set_ltchars(real_tty, p); +#endif + case TCSETSF: + return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_OLD); + case TCSETSW: + return set_termios(real_tty, p, TERMIOS_WAIT | TERMIOS_OLD); + case TCSETS: + return set_termios(real_tty, p, TERMIOS_OLD); +#ifndef TCGETS2 + case TCGETS: + copy_termios(real_tty, &kterm); + if (kernel_termios_to_user_termios((struct termios __user *)arg, &kterm)) + ret = -EFAULT; + return ret; +#else + case TCGETS: + copy_termios(real_tty, &kterm); + if (kernel_termios_to_user_termios_1((struct termios __user *)arg, &kterm)) + ret = -EFAULT; + return ret; + case TCGETS2: + copy_termios(real_tty, &kterm); + if (kernel_termios_to_user_termios((struct termios2 __user *)arg, &kterm)) + ret = -EFAULT; + return ret; + case TCSETSF2: + return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT); + case TCSETSW2: + return set_termios(real_tty, p, TERMIOS_WAIT); + case TCSETS2: + return set_termios(real_tty, p, 0); +#endif + case TCGETA: + return get_termio(real_tty, p); + case TCSETAF: + return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_TERMIO); + case TCSETAW: + return set_termios(real_tty, p, TERMIOS_WAIT | TERMIOS_TERMIO); + case TCSETA: + return set_termios(real_tty, p, TERMIOS_TERMIO); +#ifndef TCGETS2 + case TIOCGLCKTRMIOS: + copy_termios_locked(real_tty, &kterm); + if (kernel_termios_to_user_termios((struct termios __user *)arg, &kterm)) + ret = -EFAULT; + return ret; + case TIOCSLCKTRMIOS: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + copy_termios_locked(real_tty, &kterm); + if (user_termios_to_kernel_termios(&kterm, + (struct termios __user *) arg)) + return -EFAULT; + mutex_lock(&real_tty->termios_mutex); + memcpy(real_tty->termios_locked, &kterm, sizeof(struct ktermios)); + mutex_unlock(&real_tty->termios_mutex); + return 0; +#else + case TIOCGLCKTRMIOS: + copy_termios_locked(real_tty, &kterm); + if (kernel_termios_to_user_termios_1((struct termios __user *)arg, &kterm)) + ret = -EFAULT; + return ret; + case TIOCSLCKTRMIOS: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + copy_termios_locked(real_tty, &kterm); + if (user_termios_to_kernel_termios_1(&kterm, + (struct termios __user *) arg)) + return -EFAULT; + mutex_lock(&real_tty->termios_mutex); + memcpy(real_tty->termios_locked, &kterm, sizeof(struct ktermios)); + mutex_unlock(&real_tty->termios_mutex); + return ret; +#endif +#ifdef TCGETX + case TCGETX: { + struct termiox ktermx; + if (real_tty->termiox == NULL) + return -EINVAL; + mutex_lock(&real_tty->termios_mutex); + memcpy(&ktermx, real_tty->termiox, sizeof(struct termiox)); + mutex_unlock(&real_tty->termios_mutex); + if (copy_to_user(p, &ktermx, sizeof(struct termiox))) + ret = -EFAULT; + return ret; + } + case TCSETX: + return set_termiox(real_tty, p, 0); + case TCSETXW: + return set_termiox(real_tty, p, TERMIOS_WAIT); + case TCSETXF: + return set_termiox(real_tty, p, TERMIOS_FLUSH); +#endif + case TIOCGSOFTCAR: + copy_termios(real_tty, &kterm); + ret = put_user((kterm.c_cflag & CLOCAL) ? 1 : 0, + (int __user *)arg); + return ret; + case TIOCSSOFTCAR: + if (get_user(arg, (unsigned int __user *) arg)) + return -EFAULT; + return tty_change_softcar(real_tty, arg); + default: + return -ENOIOCTLCMD; + } +} +EXPORT_SYMBOL_GPL(tty_mode_ioctl); + +int tty_perform_flush(struct tty_struct *tty, unsigned long arg) +{ + struct tty_ldisc *ld; + int retval = tty_check_change(tty); + if (retval) + return retval; + + ld = tty_ldisc_ref_wait(tty); + switch (arg) { + case TCIFLUSH: + if (ld && ld->ops->flush_buffer) + ld->ops->flush_buffer(tty); + break; + case TCIOFLUSH: + if (ld && ld->ops->flush_buffer) + ld->ops->flush_buffer(tty); + /* fall through */ + case TCOFLUSH: + tty_driver_flush_buffer(tty); + break; + default: + tty_ldisc_deref(ld); + return -EINVAL; + } + tty_ldisc_deref(ld); + return 0; +} +EXPORT_SYMBOL_GPL(tty_perform_flush); + +int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + unsigned long flags; + int retval; + + switch (cmd) { + case TCXONC: + retval = tty_check_change(tty); + if (retval) + return retval; + switch (arg) { + case TCOOFF: + if (!tty->flow_stopped) { + tty->flow_stopped = 1; + stop_tty(tty); + } + break; + case TCOON: + if (tty->flow_stopped) { + tty->flow_stopped = 0; + start_tty(tty); + } + break; + case TCIOFF: + if (STOP_CHAR(tty) != __DISABLED_CHAR) + return send_prio_char(tty, STOP_CHAR(tty)); + break; + case TCION: + if (START_CHAR(tty) != __DISABLED_CHAR) + return send_prio_char(tty, START_CHAR(tty)); + break; + default: + return -EINVAL; + } + return 0; + case TCFLSH: + return tty_perform_flush(tty, arg); + case TIOCPKT: + { + int pktmode; + + if (tty->driver->type != TTY_DRIVER_TYPE_PTY || + tty->driver->subtype != PTY_TYPE_MASTER) + return -ENOTTY; + if (get_user(pktmode, (int __user *) arg)) + return -EFAULT; + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (pktmode) { + if (!tty->packet) { + tty->packet = 1; + tty->link->ctrl_status = 0; + } + } else + tty->packet = 0; + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + return 0; + } + default: + /* Try the mode commands */ + return tty_mode_ioctl(tty, file, cmd, arg); + } +} +EXPORT_SYMBOL(n_tty_ioctl_helper); diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c new file mode 100644 index 000000000000..412f9775d19c --- /dev/null +++ b/drivers/tty/tty_ldisc.c @@ -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); +} diff --git a/drivers/tty/tty_mutex.c b/drivers/tty/tty_mutex.c new file mode 100644 index 000000000000..133697540c73 --- /dev/null +++ b/drivers/tty/tty_mutex.c @@ -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); diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c new file mode 100644 index 000000000000..33d37d230f8f --- /dev/null +++ b/drivers/tty/tty_port.c @@ -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); |