/* * 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 , 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 , March 97 * * Rewrote init_dev and release_dev to eliminate races. * -- Bill Hawes , June 97 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_PROC_FS #include #endif #include #include #include #include #include #include #include #ifdef CONFIG_KERNELD #include #endif #define CONSOLE_DEV MKDEV(TTY_MAJOR,0) #define TTY_DEV MKDEV(TTYAUX_MAJOR,0) #undef TTY_DEBUG_HANGUP #define TTY_PARANOIA_CHECK 1 #define CHECK_TTY_COUNT 1 struct termios tty_std_termios; /* for the benefit of tty drivers */ struct tty_driver *tty_drivers = NULL; /* linked list of tty drivers */ struct tty_ldisc ldiscs[NR_LDISCS]; /* line disc dispatch table */ /* * redirect is the pseudo-tty that console output * is redirected to if asked by TIOCCONS. */ struct tty_struct * redirect = NULL; static void initialize_tty_struct(struct tty_struct *tty); static ssize_t tty_read(struct file *, char *, size_t, loff_t *); static ssize_t tty_write(struct file *, const char *, size_t, loff_t *); static unsigned int tty_poll(struct file *, poll_table *); static int tty_open(struct inode *, struct file *); static int tty_release(struct inode *, struct file *); static int tty_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg); static int tty_fasync(struct file * filp, int on); #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif /* * These two routines return the name of tty. tty_name() should NOT * be used in interrupt drivers, since it's not re-entrant. Use * _tty_name() instead. */ char *_tty_name(struct tty_struct *tty, char *buf) { if (tty) sprintf(buf, "%s%d", tty->driver.name, MINOR(tty->device) - tty->driver.minor_start + tty->driver.name_base); else strcpy(buf, "NULL tty"); return buf; } char *tty_name(struct tty_struct *tty) { static char buf[64]; return(_tty_name(tty, buf)); } inline int tty_paranoia_check(struct tty_struct *tty, kdev_t device, const char *routine) { #ifdef TTY_PARANOIA_CHECK static const char *badmagic = "Warning: bad magic number for tty struct (%s) in %s\n"; static const char *badtty = "Warning: null TTY for (%s) in %s\n"; if (!tty) { printk(badtty, kdevname(device), routine); return 1; } if (tty->magic != TTY_MAGIC) { printk(badmagic, kdevname(device), routine); return 1; } #endif return 0; } static int check_tty_count(struct tty_struct *tty, const char *routine) { #ifdef CHECK_TTY_COUNT struct file *f; int count = 0; for(f = inuse_filps; f; f = f->f_next) { if(f->private_data == tty) count++; } 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("Warning: dev (%s) tty->count(%d) != #fd's(%d) in %s\n", kdevname(tty->device), tty->count, count, routine); return count; } #endif return 0; } int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc) { if (disc < N_TTY || disc >= NR_LDISCS) return -EINVAL; if (new_ldisc) { ldiscs[disc] = *new_ldisc; ldiscs[disc].flags |= LDISC_FLAG_DEFINED; ldiscs[disc].num = disc; } else memset(&ldiscs[disc], 0, sizeof(struct tty_ldisc)); return 0; } /* Set the discipline of a tty line. */ static int tty_set_ldisc(struct tty_struct *tty, int ldisc) { int retval = 0; struct tty_ldisc o_ldisc; if ((ldisc < N_TTY) || (ldisc >= NR_LDISCS)) return -EINVAL; #ifdef CONFIG_KERNELD /* Eduardo Blanco */ if (!(ldiscs[ldisc].flags & LDISC_FLAG_DEFINED)) { char modname [20]; sprintf(modname, "tty-ldisc-%d", ldisc); request_module (modname); } #endif if (!(ldiscs[ldisc].flags & LDISC_FLAG_DEFINED)) return -EINVAL; if (tty->ldisc.num == ldisc) return 0; /* We are already in the desired discipline */ o_ldisc = tty->ldisc; tty_wait_until_sent(tty, 0); /* Shutdown the current discipline. */ if (tty->ldisc.close) (tty->ldisc.close)(tty); /* Now set up the new line discipline. */ tty->ldisc = ldiscs[ldisc]; tty->termios->c_line = ldisc; if (tty->ldisc.open) retval = (tty->ldisc.open)(tty); if (retval < 0) { tty->ldisc = o_ldisc; tty->termios->c_line = tty->ldisc.num; if (tty->ldisc.open && (tty->ldisc.open(tty) < 0)) { tty->ldisc = ldiscs[N_TTY]; tty->termios->c_line = N_TTY; if (tty->ldisc.open) { int r = tty->ldisc.open(tty); if (r < 0) panic("Couldn't open N_TTY ldisc for " "%s --- error %d.", tty_name(tty), r); } } } if (tty->ldisc.num != o_ldisc.num && tty->driver.set_ldisc) tty->driver.set_ldisc(tty); return retval; } /* * This routine returns a tty driver structure, given a device number */ struct tty_driver *get_tty_driver(kdev_t device) { int major, minor; struct tty_driver *p; minor = MINOR(device); major = MAJOR(device); for (p = tty_drivers; p; p = p->next) { if (p->major != major) continue; if (minor < p->minor_start) continue; if (minor >= p->minor_start + p->num) continue; return p; } return NULL; } /* * 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) */ int tty_check_change(struct tty_struct * tty) { if (current->tty != tty) return 0; if (tty->pgrp <= 0) { printk("tty_check_change: tty->pgrp <= 0!\n"); return 0; } if (current->pgrp == tty->pgrp) return 0; if (is_ignored(SIGTTOU)) return 0; if (is_orphaned_pgrp(current->pgrp)) return -EIO; (void) kill_pg(current->pgrp,SIGTTOU,1); return -ERESTARTSYS; } static ssize_t hung_up_tty_read(struct file * file, char * buf, size_t count, loff_t *ppos) { /* Can't seek (pread) on ttys. */ if (ppos != &file->f_pos) return -ESPIPE; return 0; } static ssize_t hung_up_tty_write(struct file * file, const char * buf, size_t count, loff_t *ppos) { /* Can't seek (pwrite) on ttys. */ if (ppos != &file->f_pos) return -ESPIPE; return -EIO; } static unsigned int hung_up_tty_poll(struct file * filp, poll_table * wait) { return POLLIN | POLLOUT | POLLERR | POLLHUP | POLLRDNORM | POLLWRNORM; } static int hung_up_tty_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) { return cmd == TIOCSPGRP ? -ENOTTY : -EIO; } static long long tty_lseek(struct file * file, long long offset, int orig) { return -ESPIPE; } static struct file_operations tty_fops = { tty_lseek, tty_read, tty_write, NULL, /* tty_readdir */ tty_poll, tty_ioctl, NULL, /* tty_mmap */ tty_open, tty_release, NULL, /* tty_fsync */ tty_fasync }; static struct file_operations hung_up_tty_fops = { tty_lseek, hung_up_tty_read, hung_up_tty_write, NULL, /* hung_up_tty_readdir */ hung_up_tty_poll, hung_up_tty_ioctl, NULL, /* hung_up_tty_mmap */ NULL, /* hung_up_tty_open */ tty_release, /* hung_up_tty_release */ NULL, /* hung_up_tty_fsync */ NULL /* hung_up_tty_fasync */ }; void do_tty_hangup(void *data) { struct tty_struct *tty = (struct tty_struct *) data; struct file * filp; struct task_struct *p; unsigned long flags; if (!tty) return; save_flags(flags); cli(); check_tty_count(tty, "do_tty_hangup"); for (filp = inuse_filps; filp; filp = filp->f_next) { if (filp->private_data != tty) continue; if (!filp->f_dentry) continue; if (!filp->f_dentry->d_inode) continue; if (filp->f_dentry->d_inode->i_rdev == CONSOLE_DEV) continue; if (filp->f_op != &tty_fops) continue; tty_fasync(filp, 0); filp->f_op = &hung_up_tty_fops; } if (tty->ldisc.flush_buffer) tty->ldisc.flush_buffer(tty); if (tty->driver.flush_buffer) tty->driver.flush_buffer(tty); if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); wake_up_interruptible(&tty->read_wait); /* * Shutdown the current line discipline, and reset it to * N_TTY. */ if (tty->driver.flags & TTY_DRIVER_RESET_TERMIOS) *tty->termios = tty->driver.init_termios; if (tty->ldisc.num != ldiscs[N_TTY].num) { if (tty->ldisc.close) (tty->ldisc.close)(tty); tty->ldisc = ldiscs[N_TTY]; tty->termios->c_line = N_TTY; if (tty->ldisc.open) { int i = (tty->ldisc.open)(tty); if (i < 0) printk("do_tty_hangup: N_TTY open: error %d\n", -i); } } read_lock(&tasklist_lock); for_each_task(p) { if ((tty->session > 0) && (p->session == tty->session) && p->leader) { send_sig(SIGHUP,p,1); send_sig(SIGCONT,p,1); if (tty->pgrp > 0) p->tty_old_pgrp = tty->pgrp; } if (p->tty == tty) p->tty = NULL; } read_unlock(&tasklist_lock); tty->flags = 0; tty->session = 0; tty->pgrp = -1; tty->ctrl_status = 0; if (tty->driver.hangup) (tty->driver.hangup)(tty); restore_flags(flags); } void tty_hangup(struct tty_struct * tty) { #ifdef TTY_DEBUG_HANGUP printk("%s hangup...\n", tty_name(tty)); #endif queue_task(&tty->tq_hangup, &tq_timer); } void tty_vhangup(struct tty_struct * tty) { #ifdef TTY_DEBUG_HANGUP printk("%s vhangup...\n", tty_name(tty)); #endif do_tty_hangup((void *) tty); } int tty_hung_up_p(struct file * filp) { return (filp->f_op == &hung_up_tty_fops); } /* * 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. */ void disassociate_ctty(int on_exit) { struct tty_struct *tty = current->tty; struct task_struct *p; int tty_pgrp = -1; if (tty) { tty_pgrp = tty->pgrp; if (on_exit && tty->driver.type != TTY_DRIVER_TYPE_PTY) tty_vhangup(tty); } else { if (current->tty_old_pgrp) { kill_pg(current->tty_old_pgrp, SIGHUP, on_exit); kill_pg(current->tty_old_pgrp, SIGCONT, on_exit); } return; } if (tty_pgrp > 0) { kill_pg(tty_pgrp, SIGHUP, on_exit); if (!on_exit) kill_pg(tty_pgrp, SIGCONT, on_exit); } current->tty_old_pgrp = 0; tty->session = 0; tty->pgrp = -1; read_lock(&tasklist_lock); for_each_task(p) if (p->session == current->session) p->tty = NULL; read_unlock(&tasklist_lock); } void wait_for_keypress(void) { struct console *c = console_drivers; while(c && !c->wait_key) c = c->next; if (c) c->wait_key(); } void stop_tty(struct tty_struct *tty) { if (tty->stopped) return; tty->stopped = 1; if (tty->link && tty->link->packet) { tty->ctrl_status &= ~TIOCPKT_START; tty->ctrl_status |= TIOCPKT_STOP; wake_up_interruptible(&tty->link->read_wait); } if (tty->driver.stop) (tty->driver.stop)(tty); } void start_tty(struct tty_struct *tty) { if (!tty->stopped || tty->flow_stopped) return; tty->stopped = 0; if (tty->link && tty->link->packet) { tty->ctrl_status &= ~TIOCPKT_STOP; tty->ctrl_status |= TIOCPKT_START; wake_up_interruptible(&tty->link->read_wait); } if (tty->driver.start) (tty->driver.start)(tty); if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); } static ssize_t tty_read(struct file * file, char * buf, size_t count, loff_t *ppos) { int i; struct tty_struct * tty; struct inode *inode; /* Can't seek (pread) on ttys. */ if (ppos != &file->f_pos) return -ESPIPE; tty = (struct tty_struct *)file->private_data; inode = file->f_dentry->d_inode; if (tty_paranoia_check(tty, inode->i_rdev, "tty_read")) return -EIO; if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags))) return -EIO; /* This check not only needs to be done before reading, but also whenever read_chan() gets woken up after sleeping, so I've moved it to there. This should only be done for the N_TTY line discipline, anyway. Same goes for write_chan(). -- jlc. */ #if 0 if ((inode->i_rdev != CONSOLE_DEV) && /* don't stop on /dev/console */ (tty->pgrp > 0) && (current->tty == tty) && (tty->pgrp != current->pgrp)) if (is_ignored(SIGTTIN) || is_orphaned_pgrp(current->pgrp)) return -EIO; else { (void) kill_pg(current->pgrp, SIGTTIN, 1); return -ERESTARTSYS; } #endif if (tty->ldisc.read) i = (tty->ldisc.read)(tty,file,buf,count); else i = -EIO; if (i > 0) inode->i_atime = CURRENT_TIME; return i; } /* * 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 unsigned char *buf, size_t count) { ssize_t ret = 0, written = 0; for (;;) { unsigned long size = PAGE_SIZE*2; if (size > count) size = count; ret = write(tty, file, buf, size); if (ret <= 0) break; written += ret; buf += ret; count -= ret; if (!count) break; ret = -ERESTARTSYS; if (signal_pending(current)) break; if (need_resched) schedule(); } if (written) { file->f_dentry->d_inode->i_mtime = CURRENT_TIME; ret = written; } return ret; } static ssize_t tty_write(struct file * file, const char * buf, size_t count, loff_t *ppos) { int is_console; struct tty_struct * tty; struct inode *inode; /* Can't seek (pwrite) on ttys. */ if (ppos != &file->f_pos) return -ESPIPE; inode = file->f_dentry->d_inode; is_console = (inode->i_rdev == CONSOLE_DEV); if (is_console && redirect) tty = redirect; else tty = (struct tty_struct *)file->private_data; if (tty_paranoia_check(tty, inode->i_rdev, "tty_write")) return -EIO; if (!tty || !tty->driver.write || (test_bit(TTY_IO_ERROR, &tty->flags))) return -EIO; #if 0 if (!is_console && L_TOSTOP(tty) && (tty->pgrp > 0) && (current->tty == tty) && (tty->pgrp != current->pgrp)) { if (is_orphaned_pgrp(current->pgrp)) return -EIO; if (!is_ignored(SIGTTOU)) { (void) kill_pg(current->pgrp, SIGTTOU, 1); return -ERESTARTSYS; } } #endif if (!tty->ldisc.write) return -EIO; return do_tty_write(tty->ldisc.write, tty, file, (const unsigned char *)buf, count); } /* Semaphore to protect creating and releasing a tty */ static struct semaphore tty_sem = MUTEX; static void down_tty_sem(int index) { down(&tty_sem); } static void up_tty_sem(int index) { up(&tty_sem); } static void release_mem(struct tty_struct *tty, int idx); /* * WSH 06/09/97: Rewritten to remove races and properly clean up after a * failed open. The new code protects the open with a semaphore, so it's * really quite straightforward. The semaphore locking can probably be * relaxed for the (most common) case of reopening a tty. */ static int init_dev(kdev_t device, struct tty_struct **ret_tty) { struct tty_struct *tty, *o_tty; struct termios *tp, **tp_loc, *o_tp, **o_tp_loc; struct termios *ltp, **ltp_loc, *o_ltp, **o_ltp_loc; struct tty_driver *driver; int retval=0; int idx; driver = get_tty_driver(device); if (!driver) return -ENODEV; idx = MINOR(device) - driver->minor_start; /* * Check whether we need to acquire the tty semaphore to avoid * race conditions. For now, play it safe. */ down_tty_sem(idx); /* check whether we're reopening an existing tty */ tty = driver->table[idx]; if (tty) goto fast_track; /* * 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.) */ o_tty = NULL; tp = o_tp = NULL; ltp = o_ltp = NULL; tty = (struct tty_struct*) get_free_page(GFP_KERNEL); if(!tty) goto fail_no_mem; initialize_tty_struct(tty); tty->device = device; tty->driver = *driver; tp_loc = &driver->termios[idx]; if (!*tp_loc) { tp = (struct termios *) kmalloc(sizeof(struct termios), GFP_KERNEL); if (!tp) goto free_mem_out; *tp = driver->init_termios; } ltp_loc = &driver->termios_locked[idx]; if (!*ltp_loc) { ltp = (struct termios *) kmalloc(sizeof(struct termios), GFP_KERNEL); if (!ltp) goto free_mem_out; memset(ltp, 0, sizeof(struct termios)); } if (driver->type == TTY_DRIVER_TYPE_PTY) { o_tty = (struct tty_struct *) get_free_page(GFP_KERNEL); if (!o_tty) goto free_mem_out; initialize_tty_struct(o_tty); o_tty->device = (kdev_t) MKDEV(driver->other->major, driver->other->minor_start + idx); o_tty->driver = *driver->other; o_tp_loc = &driver->other->termios[idx]; if (!*o_tp_loc) { o_tp = (struct termios *) kmalloc(sizeof(struct termios), GFP_KERNEL); if (!o_tp) goto free_mem_out; *o_tp = driver->other->init_termios; } o_ltp_loc = &driver->other->termios_locked[idx]; if (!*o_ltp_loc) { o_ltp = (struct termios *) kmalloc(sizeof(struct termios), GFP_KERNEL); if (!o_ltp) goto free_mem_out; memset(o_ltp, 0, sizeof(struct termios)); } /* * Everything allocated ... set up the o_tty structure. */ driver->other->table[idx] = o_tty; if (!*o_tp_loc) *o_tp_loc = o_tp; if (!*o_ltp_loc) *o_ltp_loc = o_ltp; o_tty->termios = *o_tp_loc; o_tty->termios_locked = *o_ltp_loc; (*driver->other->refcount)++; 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_mem to clean up, so * there's no need to null out the local pointers. */ driver->table[idx] = tty; if (!*tp_loc) *tp_loc = tp; if (!*ltp_loc) *ltp_loc = ltp; tty->termios = *tp_loc; tty->termios_locked = *ltp_loc; (*driver->refcount)++; tty->count++; /* * Structures all installed ... call the ldisc open routines. * If we fail here just call release_mem to clean up. No need * to decrement the use counts, as release_mem doesn't care. */ if (tty->ldisc.open) { retval = (tty->ldisc.open)(tty); if (retval) goto release_mem_out; } if (o_tty && o_tty->ldisc.open) { retval = (o_tty->ldisc.open)(o_tty); if (retval) { if (tty->ldisc.close) (tty->ldisc.close)(tty); goto release_mem_out; } } goto success; /* * This fast open can be used if the tty is already open. * No memory is allocated, and the only failures are from * attempting to open a closing tty or attempting multiple * opens on a pty master. */ fast_track: if (test_bit(TTY_CLOSING, &tty->flags)) { retval = -EIO; goto end_init; } 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) { retval = -EIO; goto end_init; } tty->link->count++; } tty->count++; tty->driver = *driver; /* N.B. why do this every time?? */ success: *ret_tty = tty; /* All paths come through here to release the semaphore */ end_init: up_tty_sem(idx); return retval; /* Release locally allocated memory ... nothing placed in slots */ free_mem_out: if (o_tp) kfree_s(o_tp, sizeof(struct termios)); if (o_tty) free_page((unsigned long) o_tty); if (ltp) kfree_s(ltp, sizeof(struct termios)); if (tp) kfree_s(tp, sizeof(struct termios)); free_page((unsigned long) tty); fail_no_mem: retval = -ENOMEM; goto end_init; /* call the tty release_mem routine to clean out this slot */ release_mem_out: printk("init_dev: ldisc open failed, clearing slot %d\n", idx); release_mem(tty, idx); goto end_init; } /* * Releases memory associated with a tty structure, and clears out the * driver table slots. */ static void release_mem(struct tty_struct *tty, int idx) { struct tty_struct *o_tty; struct termios *tp; if ((o_tty = tty->link) != NULL) { o_tty->driver.table[idx] = NULL; if (o_tty->driver.flags & TTY_DRIVER_RESET_TERMIOS) { tp = o_tty->driver.termios[idx]; o_tty->driver.termios[idx] = NULL; kfree_s(tp, sizeof(struct termios)); } o_tty->magic = 0; (*o_tty->driver.refcount)--; free_page((unsigned long) o_tty); } tty->driver.table[idx] = NULL; if (tty->driver.flags & TTY_DRIVER_RESET_TERMIOS) { tp = tty->driver.termios[idx]; tty->driver.termios[idx] = NULL; kfree_s(tp, sizeof(struct termios)); } tty->magic = 0; (*tty->driver.refcount)--; free_page((unsigned long) tty); } /* * 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. */ static void release_dev(struct file * filp) { struct tty_struct *tty, *o_tty; int pty_master, tty_closing, o_tty_closing, do_sleep; int idx; tty = (struct tty_struct *)filp->private_data; if (tty_paranoia_check(tty, filp->f_dentry->d_inode->i_rdev, "release_dev")) return; check_tty_count(tty, "release_dev"); tty_fasync(filp, 0); idx = MINOR(tty->device) - tty->driver.minor_start; pty_master = (tty->driver.type == TTY_DRIVER_TYPE_PTY && tty->driver.subtype == PTY_TYPE_MASTER); o_tty = tty->link; #ifdef TTY_PARANOIA_CHECK if (idx < 0 || idx >= tty->driver.num) { printk("release_dev: bad idx when trying to free (%s)\n", kdevname(tty->device)); return; } if (tty != tty->driver.table[idx]) { printk("release_dev: driver.table[%d] not tty for (%s)\n", idx, kdevname(tty->device)); return; } if (tty->termios != tty->driver.termios[idx]) { printk("release_dev: driver.termios[%d] not termios " "for (%s)\n", idx, kdevname(tty->device)); return; } if (tty->termios_locked != tty->driver.termios_locked[idx]) { printk("release_dev: driver.termios_locked[%d] not " "termios_locked for (%s)\n", idx, kdevname(tty->device)); return; } #endif #ifdef TTY_DEBUG_HANGUP printk("release_dev of %s (tty count=%d)...", tty_name(tty), tty->count); #endif #ifdef TTY_PARANOIA_CHECK if (tty->driver.other) { if (o_tty != tty->driver.other->table[idx]) { printk("release_dev: other->table[%d] not o_tty for (" "%s)\n", idx, kdevname(tty->device)); return; } if (o_tty->termios != tty->driver.other->termios[idx]) { printk("release_dev: other->termios[%d] not o_termios " "for (%s)\n", idx, kdevname(tty->device)); return; } if (o_tty->termios_locked != tty->driver.other->termios_locked[idx]) { printk("release_dev: other->termios_locked[%d] not " "o_termios_locked for (%s)\n", idx, kdevname(tty->device)); return; } if (o_tty->link != tty) { printk("release_dev: bad pty pointers\n"); return; } } #endif /* * 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) { 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(&tty->read_wait); do_sleep++; } if (waitqueue_active(&tty->write_wait)) { wake_up(&tty->write_wait); do_sleep++; } } if (o_tty_closing) { if (waitqueue_active(&o_tty->read_wait)) { wake_up(&o_tty->read_wait); do_sleep++; } if (waitqueue_active(&o_tty->write_wait)) { wake_up(&o_tty->write_wait); do_sleep++; } } if (!do_sleep) break; printk("release_dev: %s: read/write wait queue active!\n", tty_name(tty)); 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 (tty->driver.close) tty->driver.close(tty, filp); if (pty_master) { if (--o_tty->count < 0) { printk("release_dev: bad pty slave count (%d) for %s\n", o_tty->count, tty_name(o_tty)); o_tty->count = 0; } } if (--tty->count < 0) { printk("release_dev: bad tty->count (%d) for %s\n", tty->count, tty_name(tty)); tty->count = 0; } /* * 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. Also, clear redirect if it points to either tty. */ if (tty_closing || o_tty_closing) { struct task_struct *p; read_lock(&tasklist_lock); for_each_task(p) { if (p->tty == tty || (o_tty && p->tty == o_tty)) p->tty = NULL; } read_unlock(&tasklist_lock); if (redirect == tty || (o_tty && redirect == o_tty)) redirect = NULL; } /* check whether both sides are closing ... */ if (!tty_closing || (o_tty && !o_tty_closing)) return; filp->private_data = 0; #ifdef TTY_DEBUG_HANGUP printk("freeing tty structure..."); #endif /* * Shutdown the current line discipline, and reset it to N_TTY. * N.B. why reset ldisc when we're releasing the memory?? */ if (tty->ldisc.close) (tty->ldisc.close)(tty); tty->ldisc = ldiscs[N_TTY]; tty->termios->c_line = N_TTY; if (o_tty) { if (o_tty->ldisc.close) (o_tty->ldisc.close)(o_tty); o_tty->ldisc = ldiscs[N_TTY]; } /* * Make sure that the tty's task queue isn't activated. */ run_task_queue(&tq_timer); /* * The release_mem function takes care of the details of clearing * the slots and preserving the termios structure. */ release_mem(tty, idx); } /* * 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. */ static int tty_open(struct inode * inode, struct file * filp) { struct tty_struct *tty; int minor; int noctty, retval; kdev_t device; unsigned short saved_flags; saved_flags = filp->f_flags; retry_open: noctty = filp->f_flags & O_NOCTTY; device = inode->i_rdev; if (device == TTY_DEV) { if (!current->tty) return -ENXIO; device = current->tty->device; filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */ /* noctty = 1; */ } if (device == CONSOLE_DEV) { struct console *c = console_drivers; while(c && !c->device) c = c->next; if (!c) return -ENODEV; device = c->device(); noctty = 1; } minor = MINOR(device); retval = init_dev(device, &tty); if (retval) return retval; /* N.B. this error exit may leave filp->f_flags with O_NONBLOCK set */ filp->private_data = tty; 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("opening %s...", tty_name(tty)); #endif if (tty->driver.open) retval = tty->driver.open(tty, filp); else retval = -ENODEV; filp->f_flags = saved_flags; if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && !suser()) retval = -EBUSY; if (retval) { #ifdef TTY_DEBUG_HANGUP printk("error %d in opening %s...", retval, tty_name(tty)); #endif release_dev(filp); if (retval != -ERESTARTSYS) return retval; if (signal_pending(current)) return retval; schedule(); /* * Need to reset f_op in case a hangup happened. */ filp->f_op = &tty_fops; goto retry_open; } if (!noctty && current->leader && !current->tty && tty->session == 0) { current->tty = tty; current->tty_old_pgrp = 0; tty->session = current->session; tty->pgrp = current->pgrp; } return 0; } static int tty_release(struct inode * inode, struct file * filp) { release_dev(filp); return 0; } static unsigned int tty_poll(struct file * filp, poll_table * wait) { struct tty_struct * tty; tty = (struct tty_struct *)filp->private_data; if (tty_paranoia_check(tty, filp->f_dentry->d_inode->i_rdev, "tty_poll")) return 0; if (tty->ldisc.poll) return (tty->ldisc.poll)(tty, filp, wait); return 0; } /* * fasync_helper() is used by some character device drivers (mainly mice) * to set up the fasync queue. It returns negative on error, 0 if it did * no changes and positive if it added/deleted the entry. */ int fasync_helper(struct file * filp, int on, struct fasync_struct **fapp) { struct fasync_struct *fa, **fp; unsigned long flags; for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { if (fa->fa_file == filp) break; } if (on) { if (fa) return 0; fa = (struct fasync_struct *)kmalloc(sizeof(struct fasync_struct), GFP_KERNEL); if (!fa) return -ENOMEM; fa->magic = FASYNC_MAGIC; fa->fa_file = filp; save_flags(flags); cli(); fa->fa_next = *fapp; *fapp = fa; restore_flags(flags); return 1; } if (!fa) return 0; save_flags(flags); cli(); *fp = fa->fa_next; restore_flags(flags); kfree(fa); return 1; } static int tty_fasync(struct file * filp, int on) { struct tty_struct * tty; int retval; tty = (struct tty_struct *)filp->private_data; if (tty_paranoia_check(tty, filp->f_dentry->d_inode->i_rdev, "tty_fasync")) return 0; retval = fasync_helper(filp, on, &tty->fasync); if (retval <= 0) return retval; if (on) { if (!waitqueue_active(&tty->read_wait)) tty->minimum_to_wake = 1; if (filp->f_owner.pid == 0) { filp->f_owner.pid = (-tty->pgrp) ? : current->pid; filp->f_owner.uid = current->uid; filp->f_owner.euid = current->euid; } } else { if (!tty->fasync && !waitqueue_active(&tty->read_wait)) tty->minimum_to_wake = N_TTY_BUF_SIZE; } return 0; } static int tiocsti(struct tty_struct *tty, char * arg) { char ch, mbz = 0; if ((current->tty != tty) && !suser()) return -EPERM; if (get_user(ch, arg)) return -EFAULT; tty->ldisc.receive_buf(tty, &ch, &mbz, 1); return 0; } static int tiocgwinsz(struct tty_struct *tty, struct winsize * arg) { if (copy_to_user(arg, &tty->winsize, sizeof(*arg))) return -EFAULT; return 0; } static int tiocswinsz(struct tty_struct *tty, struct tty_struct *real_tty, struct winsize * arg) { struct winsize tmp_ws; if (copy_from_user(&tmp_ws, arg, sizeof(*arg))) return -EFAULT; if (!memcmp(&tmp_ws, &tty->winsize, sizeof(*arg))) return 0; if (tty->pgrp > 0) kill_pg(tty->pgrp, SIGWINCH, 1); if ((real_tty->pgrp != tty->pgrp) && (real_tty->pgrp > 0)) kill_pg(real_tty->pgrp, SIGWINCH, 1); tty->winsize = tmp_ws; real_tty->winsize = tmp_ws; return 0; } static int tioccons(struct tty_struct *tty, struct tty_struct *real_tty) { if (tty->driver.type == TTY_DRIVER_TYPE_CONSOLE) { if (!suser()) return -EPERM; redirect = NULL; return 0; } if (redirect) return -EBUSY; redirect = real_tty; return 0; } static int fionbio(struct file *file, int *arg) { int nonblock; if (get_user(nonblock, arg)) return -EFAULT; if (nonblock) file->f_flags |= O_NONBLOCK; else file->f_flags &= ~O_NONBLOCK; return 0; } static int tiocsctty(struct tty_struct *tty, int arg) { if (current->leader && (current->session == tty->session)) return 0; /* * The process must be a session leader and * not have a controlling tty already. */ if (!current->leader || current->tty) return -EPERM; if (tty->session > 0) { /* * This tty is already the controlling * tty for another session group! */ if ((arg == 1) && suser()) { /* * Steal it away */ struct task_struct *p; read_lock(&tasklist_lock); for_each_task(p) if (p->tty == tty) p->tty = NULL; read_unlock(&tasklist_lock); } else return -EPERM; } current->tty = tty; current->tty_old_pgrp = 0; tty->session = current->session; tty->pgrp = current->pgrp; return 0; } static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t *arg) { /* * (tty == real_tty) is a cheap way of * testing if the tty is NOT a master pty. */ if (tty == real_tty && current->tty != real_tty) return -ENOTTY; return put_user(real_tty->pgrp, arg); } static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t *arg) { pid_t pgrp; int retval = tty_check_change(real_tty); if (retval == -EIO) return -ENOTTY; if (retval) return retval; if (!current->tty || (current->tty != real_tty) || (real_tty->session != current->session)) return -ENOTTY; get_user(pgrp, (pid_t *) arg); if (pgrp < 0) return -EINVAL; if (session_of_pgrp(pgrp) != current->session) return -EPERM; real_tty->pgrp = pgrp; return 0; } static int tiocttygstruct(struct tty_struct *tty, struct tty_struct *arg) { if (copy_to_user(arg, tty, sizeof(*arg))) return -EFAULT; return 0; } static int tiocsetd(struct tty_struct *tty, int *arg) { int retval, ldisc; retval = get_user(ldisc, arg); if (retval) return retval; return tty_set_ldisc(tty, ldisc); } static int send_break(struct tty_struct *tty, int duration) { current->state = TASK_INTERRUPTIBLE; current->timeout = jiffies + duration; tty->driver.break_ctl(tty, -1); if (!signal_pending(current)) schedule(); tty->driver.break_ctl(tty, 0); if (signal_pending(current)) return -EINTR; return 0; } /* * Split this up, as gcc can choke on it otherwise.. */ static int tty_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) { struct tty_struct *tty, *real_tty; int retval; tty = (struct tty_struct *)file->private_data; if (tty_paranoia_check(tty, inode->i_rdev, "tty_ioctl")) return -EINVAL; real_tty = tty; if (tty->driver.type == TTY_DRIVER_TYPE_PTY && tty->driver.subtype == PTY_TYPE_MASTER) real_tty = tty->link; /* * Break handling by driver */ if (!tty->driver.break_ctl) { switch(cmd) { case TIOCSBRK: case TIOCCBRK: return tty->driver.ioctl(tty, file, cmd, arg); /* These two ioctl's always return success; even if */ /* the driver doesn't support them. */ case TCSBRK: case TCSBRKP: retval = tty->driver.ioctl(tty, file, cmd, arg); if (retval == -ENOIOCTLCMD) retval = 0; return retval; } } /* * 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; } switch (cmd) { case TIOCSTI: return tiocsti(tty, (char *)arg); case TIOCGWINSZ: return tiocgwinsz(tty, (struct winsize *) arg); case TIOCSWINSZ: return tiocswinsz(tty, real_tty, (struct winsize *) arg); case TIOCCONS: return tioccons(tty, real_tty); case FIONBIO: return fionbio(file, (int *) arg); case TIOCEXCL: set_bit(TTY_EXCLUSIVE, &tty->flags); return 0; case TIOCNXCL: clear_bit(TTY_EXCLUSIVE, &tty->flags); return 0; case TIOCNOTTY: if (current->tty != tty) return -ENOTTY; if (current->leader) disassociate_ctty(0); current->tty = NULL; return 0; case TIOCSCTTY: return tiocsctty(tty, arg); case TIOCGPGRP: return tiocgpgrp(tty, real_tty, (pid_t *) arg); case TIOCSPGRP: return tiocspgrp(tty, real_tty, (pid_t *) arg); case TIOCGETD: return put_user(tty->ldisc.num, (int *) arg); case TIOCSETD: return tiocsetd(tty, (int *) arg); #ifdef CONFIG_VT case TIOCLINUX: return tioclinux(tty, arg); #endif case TIOCTTYGSTRUCT: return tiocttygstruct(tty, (struct tty_struct *) arg); /* * Break handling */ case TIOCSBRK: /* Turn break on, unconditionally */ tty->driver.break_ctl(tty, -1); return 0; case TIOCCBRK: /* Turn break off, unconditionally */ tty->driver.break_ctl(tty, 0); return 0; case TCSBRK: /* SVID version: non-zero arg --> no break */ /* * XXX is the above comment correct, or the * code below correct? Is this ioctl used at * all by anyone? */ if (!arg) return send_break(tty, HZ/4); return 0; case TCSBRKP: /* support for POSIX tcsendbreak() */ return send_break(tty, arg ? arg*(HZ/10) : HZ/4); } if (tty->driver.ioctl) { int retval = (tty->driver.ioctl)(tty, file, cmd, arg); if (retval != -ENOIOCTLCMD) return retval; } if (tty->ldisc.ioctl) { int retval = (tty->ldisc.ioctl)(tty, file, cmd, arg); if (retval != -ENOIOCTLCMD) return retval; } return -EINVAL; } /* * 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. */ void do_SAK( struct tty_struct *tty) { #ifdef TTY_SOFT_SAK tty_hangup(tty); #else struct task_struct *p; int session; int i; struct file *filp; if (!tty) return; session = tty->session; if (tty->ldisc.flush_buffer) tty->ldisc.flush_buffer(tty); if (tty->driver.flush_buffer) tty->driver.flush_buffer(tty); read_lock(&tasklist_lock); for_each_task(p) { if ((p->tty == tty) || ((session > 0) && (p->session == session))) send_sig(SIGKILL, p, 1); else if (p->files) { for (i=0; i < NR_OPEN; i++) { filp = p->files->fd[i]; if (filp && (filp->f_op == &tty_fops) && (filp->private_data == tty)) { send_sig(SIGKILL, p, 1); break; } } } } read_unlock(&tasklist_lock); #endif } /* * This routine is called out of the software interrupt to flush data * from the flip buffer to the line discipline. */ static void flush_to_ldisc(void *private_) { struct tty_struct *tty = (struct tty_struct *) private_; unsigned char *cp; char *fp; int count; unsigned long flags; if (tty->flip.buf_num) { cp = tty->flip.char_buf + TTY_FLIPBUF_SIZE; fp = tty->flip.flag_buf + TTY_FLIPBUF_SIZE; tty->flip.buf_num = 0; save_flags(flags); cli(); tty->flip.char_buf_ptr = tty->flip.char_buf; tty->flip.flag_buf_ptr = tty->flip.flag_buf; } else { cp = tty->flip.char_buf; fp = tty->flip.flag_buf; tty->flip.buf_num = 1; save_flags(flags); cli(); tty->flip.char_buf_ptr = tty->flip.char_buf + TTY_FLIPBUF_SIZE; tty->flip.flag_buf_ptr = tty->flip.flag_buf + TTY_FLIPBUF_SIZE; } count = tty->flip.count; tty->flip.count = 0; restore_flags(flags); tty->ldisc.receive_buf(tty, cp, fp, count); } /* * Routine which returns the baud rate of the tty */ /* * This is used to figure out the divisor speeds and the timeouts */ static int baud_table[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 0 }; int tty_get_baud_rate(struct tty_struct *tty) { unsigned int cflag, i; cflag = tty->termios->c_cflag; i = cflag & CBAUD; if (i & CBAUDEX) { i &= ~CBAUDEX; if (i < 1 || i > 4) tty->termios->c_cflag &= ~CBAUDEX; else i += 15; } if (i==15 && tty->alt_speed) return(tty->alt_speed); return baud_table[i]; } void tty_flip_buffer_push(struct tty_struct *tty) { if (tty->low_latency) flush_to_ldisc((void *) tty); else queue_task(&tty->flip.tqueue, &tq_timer); } /* * This subroutine initializes a tty structure. */ static void initialize_tty_struct(struct tty_struct *tty) { memset(tty, 0, sizeof(struct tty_struct)); tty->magic = TTY_MAGIC; tty->ldisc = ldiscs[N_TTY]; tty->pgrp = -1; tty->flip.char_buf_ptr = tty->flip.char_buf; tty->flip.flag_buf_ptr = tty->flip.flag_buf; tty->flip.tqueue.routine = flush_to_ldisc; tty->flip.tqueue.data = tty; tty->flip.pty_sem = MUTEX; tty->tq_hangup.routine = do_tty_hangup; tty->tq_hangup.data = tty; } /* * The default put_char routine if the driver did not define one. */ void tty_default_put_char(struct tty_struct *tty, unsigned char ch) { tty->driver.write(tty, 0, &ch, 1); } /* * Called by a tty driver to register itself. */ int tty_register_driver(struct tty_driver *driver) { int error; if (driver->flags & TTY_DRIVER_INSTALLED) return 0; error = register_chrdev(driver->major, driver->name, &tty_fops); if (error < 0) return error; else if(driver->major == 0) driver->major = error; if (!driver->put_char) driver->put_char = tty_default_put_char; driver->prev = 0; driver->next = tty_drivers; if (tty_drivers) tty_drivers->prev = driver; tty_drivers = driver; #ifdef CONFIG_PROC_FS proc_tty_register_driver(driver); #endif return error; } /* * Called by a tty driver to unregister itself. */ int tty_unregister_driver(struct tty_driver *driver) { int retval; struct tty_driver *p; int found = 0; const char *othername = NULL; if (*driver->refcount) return -EBUSY; for (p = tty_drivers; p; p = p->next) { if (p == driver) found++; else if (p->major == driver->major) othername = p->name; } if (!found) return -ENOENT; if (othername == NULL) { retval = unregister_chrdev(driver->major, driver->name); if (retval) return retval; } else register_chrdev(driver->major, othername, &tty_fops); if (driver->prev) driver->prev->next = driver->next; else tty_drivers = driver->next; if (driver->next) driver->next->prev = driver->prev; #ifdef CONFIG_PROC_FS proc_tty_unregister_driver(driver); #endif return 0; } /* * 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. */ long console_init(long kmem_start, long kmem_end) { /* Setup the default TTY line discipline. */ memset(ldiscs, 0, sizeof(ldiscs)); (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY); /* * Set up the standard termios. Individual tty drivers may * deviate from this; this is used as a template. */ memset(&tty_std_termios, 0, sizeof(struct termios)); memcpy(tty_std_termios.c_cc, INIT_C_CC, NCCS); tty_std_termios.c_iflag = ICRNL | IXON; tty_std_termios.c_oflag = OPOST | ONLCR; tty_std_termios.c_cflag = B38400 | CS8 | CREAD | HUPCL; tty_std_termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; /* * set up the console device so that later boot sequences can * inform about problems etc.. */ #ifdef CONFIG_SERIAL_CONSOLE kmem_start = serial_console_init(kmem_start, kmem_end); #endif #ifdef CONFIG_VT kmem_start = con_init(kmem_start); #endif return kmem_start; } static struct tty_driver dev_tty_driver, dev_console_driver; /* * Ok, now we can initialize the rest of the tty devices and can count * on memory allocations, interrupts etc.. */ __initfunc(int tty_init(void)) { if (sizeof(struct tty_struct) > PAGE_SIZE) panic("size of tty structure > PAGE_SIZE!"); /* * dev_tty_driver and dev_console_driver are actually magic * devices which get redirected at open time. Nevertheless, * we register them so that register_chrdev is called * appropriately. */ memset(&dev_tty_driver, 0, sizeof(struct tty_driver)); dev_tty_driver.magic = TTY_DRIVER_MAGIC; dev_tty_driver.driver_name = "/dev/tty"; dev_tty_driver.name = dev_tty_driver.driver_name + 5; dev_tty_driver.name_base = 0; dev_tty_driver.major = TTYAUX_MAJOR; dev_tty_driver.minor_start = 0; dev_tty_driver.num = 1; dev_tty_driver.type = TTY_DRIVER_TYPE_SYSTEM; dev_tty_driver.subtype = SYSTEM_TYPE_TTY; if (tty_register_driver(&dev_tty_driver)) panic("Couldn't register /dev/tty driver\n"); dev_console_driver = dev_tty_driver; dev_console_driver.driver_name = "/dev/console"; dev_console_driver.name = dev_console_driver.driver_name + 5; dev_console_driver.major = TTY_MAJOR; dev_console_driver.type = TTY_DRIVER_TYPE_SYSTEM; dev_console_driver.subtype = SYSTEM_TYPE_CONSOLE; if (tty_register_driver(&dev_console_driver)) panic("Couldn't register /dev/console driver\n"); #ifdef CONFIG_VT kbd_init(); #endif #ifdef CONFIG_ESPSERIAL /* init ESP before rs, so rs doesn't see the port */ espserial_init(); #endif #ifdef CONFIG_SERIAL rs_init(); #endif #ifdef CONFIG_ROCKETPORT rp_init(); #endif #ifdef CONFIG_CYCLADES cy_init(); #endif #ifdef CONFIG_STALLION stl_init(); #endif #ifdef CONFIG_ISTALLION stli_init(); #endif #ifdef CONFIG_DIGI pcxe_init(); #endif #ifdef CONFIG_DIGIEPCA pc_init(); #endif #ifdef CONFIG_RISCOM8 riscom8_init(); #endif pty_init(); #ifdef CONFIG_VT vcs_init(); #endif return 0; }