n_tty.c
字号:
/* * 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 write_chan 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 <asm/uaccess.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 128static inline unsigned char *alloc_buf(void){ gfp_t prio = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL; if (PAGE_SIZE != N_TTY_BUF_SIZE) return kmalloc(N_TTY_BUF_SIZE, prio); else return (unsigned char *)__get_free_page(prio);}static inline void free_buf(unsigned char *buf){ if (PAGE_SIZE != N_TTY_BUF_SIZE) kfree(buf); else free_page((unsigned long) buf);}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){ 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++; }}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 function. * We test the TTY_THROTTLED bit first so that it always * indicates the current state. The decision about whether * it is worth allowing more input has been taken by the caller. * 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 && test_and_clear_bit(TTY_THROTTLED, &tty->flags) && tty->driver->unthrottle) tty->driver->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(). */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); 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). * * FIXME: tty->ctrl_status is not spinlocked and relies on * lock_kernel() still. */ static void n_tty_flush_buffer(struct tty_struct * tty){ /* clear everything and unthrottle the driver */ reset_buffer_flags(tty); if (!tty->link) return; if (tty->link->packet) { tty->ctrl_status |= TIOCPKT_FLUSHREAD; wake_up_interruptible(&tty->link->read_wait); }}/** * 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. */ 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);}/** * opost - output post processor * @c: character (or partial unicode symbol) * @tty: terminal device * * Perform OPOST processing. Returns -1 when the output device is * full and the character must be retried. 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. * * Called from both the receive and transmit sides and can be called * re-entrantly. Relies on lock_kernel() still. */ static int opost(unsigned char c, struct tty_struct *tty){ int space, spaces; space = tty->driver->write_room(tty); if (!space) return -1; if (O_OPOST(tty)) { switch (c) { case '\n': if (O_ONLRET(tty)) tty->column = 0; if (O_ONLCR(tty)) { if (space < 2) return -1; tty->driver->put_char(tty, '\r'); tty->column = 0; } 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->driver->write(tty, " ", spaces); return 0; } tty->column += spaces; break; case '\b': if (tty->column > 0) tty->column--; break; default: if (O_OLCUC(tty)) c = toupper(c); if (!iscntrl(c) && !is_continuation(c, tty)) tty->column++; break; } } tty->driver->put_char(tty, c); return 0;}/** * opost_block - block postprocess * @tty: terminal device * @inbuf: user buffer * @nr: number of bytes * * 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. * * Called from write_chan under the tty layer write lock. */ static ssize_t opost_block(struct tty_struct * tty, const unsigned char * buf, unsigned int nr){ int space; int i; const unsigned char *cp; space = tty->driver->write_room(tty); if (!space) return 0; if (nr > space) nr = space; for (i = 0, cp = buf; i < nr; i++, cp++) { switch (*cp) { 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 (O_OLCUC(tty)) goto break_out; if (!iscntrl(*cp)) tty->column++; break; } }break_out: if (tty->driver->flush_chars) tty->driver->flush_chars(tty); i = tty->driver->write(tty, buf, i); return i;}/** * put_char - write character to driver * @c: character (or part of unicode symbol) * @tty: terminal device * * Queue a byte to the driver layer for output */ static inline void put_char(unsigned char c, struct tty_struct *tty){ tty->driver->put_char(tty, c);}/** * echo_char - echo characters * @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. */static void echo_char(unsigned char c, struct tty_struct *tty){ if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t') { put_char('^', tty); put_char(c ^ 0100, tty); tty->column += 2; } else opost(c, tty);}static inline void finish_erasing(struct tty_struct *tty){ if (tty->erasing) { put_char('/', tty); tty->column++; 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. */ static void eraser(unsigned char c, struct tty_struct *tty){ enum { ERASE, WERASE, KILL } kill_type; int head, seen_alnums, cnt; unsigned long flags; if (tty->read_head == tty->canon_head) { /* opost('\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)) opost('\n', tty); return; } kill_type = KILL; } seen_alnums = 0; 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) { put_char('\\', tty); tty->column++; 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); put_char(tty->read_buf[head], tty); } } else if (kill_type == ERASE && !L_ECHOE(tty)) { echo_char(ERASE_CHAR(tty), tty); } else if (c == '\t') { unsigned int col = tty->canon_column;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -