X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fchar%2Ftty_io.c;h=0b31cf25c25c2b535a4790f242cd4c3fc61fc8b2;hb=43bc926fffd92024b46cafaf7350d669ba9ca884;hp=91f530c6170ee73ba76f3b12bfd517518289fcaa;hpb=9bf4aaab3e101692164d49b7ca357651eb691cb6;p=linux-2.6.git diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index 91f530c61..0b31cf25c 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c @@ -92,15 +92,18 @@ #include #include #include +#include +#include +#include #include #include -#include #include #include #include #include +#include #include @@ -120,17 +123,22 @@ struct termios tty_std_termios = { /* for the benefit of tty drivers */ 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 */ -struct tty_ldisc ldiscs[NR_LDISCS]; /* line disc dispatch table */ -/* Semaphore to protect creating and releasing a tty */ -DECLARE_MUTEX(tty_sem); +/* Semaphore to protect creating and releasing a tty. This is shared with + vt.c for deeply disgusting hack reasons */ +DEFINE_MUTEX(tty_mutex); #ifdef CONFIG_UNIX98_PTYS extern struct tty_driver *ptm_driver; /* Unix98 pty masters; for /dev/ptmx */ extern int pty_limit; /* Config limit on Unix98 ptys */ static DEFINE_IDR(allocated_ptys); static DECLARE_MUTEX(allocated_ptys_lock); +static int ptmx_open(struct inode *, struct file *); #endif extern void disable_early_printk(void); @@ -146,7 +154,6 @@ static int tty_release(struct inode *, struct file *); int tty_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg); static int tty_fasync(int fd, struct file * filp, int on); -extern void rs_360_init(void); static void release_mem(struct tty_struct *tty, int idx); @@ -160,8 +167,12 @@ static struct tty_struct *alloc_tty_struct(void) return tty; } +static void tty_buffer_free_all(struct tty_struct *); + static inline void free_tty_struct(struct tty_struct *tty) { + kfree(tty->write_buf); + tty_buffer_free_all(tty); kfree(tty); } @@ -178,7 +189,7 @@ char *tty_name(struct tty_struct *tty, char *buf) EXPORT_SYMBOL(tty_name); -inline int tty_paranoia_check(struct tty_struct *tty, struct inode *inode, +int tty_paranoia_check(struct tty_struct *tty, struct inode *inode, const char *routine) { #ifdef TTY_PARANOIA_CHECK @@ -223,65 +234,595 @@ static int check_tty_count(struct tty_struct *tty, const char *routine) return 0; } +/* + * Tty buffer allocation management + */ + +static 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; +} + +static 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; +} + +static struct tty_buffer *tty_buffer_alloc(size_t size) +{ + struct tty_buffer *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->active = 0; + p->commit = 0; + p->read = 0; + p->char_buf_ptr = (char *)(p->data); + p->flag_buf_ptr = (unsigned char *)p->char_buf_ptr + size; +/* printk("Flip create %p\n", p); */ + return p; +} + +/* Must be called with the tty_read lock held. This needs to acquire strategy + code to decide if we should kfree or relink a given expired buffer */ + +static void tty_buffer_free(struct tty_struct *tty, struct tty_buffer *b) +{ + /* Dumb strategy for now - should keep some stats */ +/* printk("Flip dispose %p\n", b); */ + if(b->size >= 512) + kfree(b); + else { + b->next = tty->buf.free; + tty->buf.free = b; + } +} + +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; + /* DEBUG ONLY */ +/* memset(t->data, '*', size); */ +/* printk("Flip recycle %p\n", t); */ + return t; + } + tbh = &((*tbh)->next); + } + /* Round the buffer size out */ + size = (size + 0xFF) & ~ 0xFF; + return tty_buffer_alloc(size); + /* Should possibly check if this fails for the largest buffer we + have queued and recycle that ? */ +} + +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; + b->active = 1; + } 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->active = 0; + b->commit = b->used; + } else + tty->buf.head = n; + tty->buf.tail = n; + n->active = 1; + } else + size = left; + } + + spin_unlock_irqrestore(&tty->buf.lock, flags); + return size; +} +EXPORT_SYMBOL_GPL(tty_buffer_request_room); + +int tty_insert_flip_string(struct tty_struct *tty, const unsigned char *chars, + size_t size) +{ + int copied = 0; + do { + int space = tty_buffer_request_room(tty, size - copied); + 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, TTY_NORMAL, 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); + +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 space = tty_buffer_request_room(tty, size - copied); + 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); + +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->active = 0; + 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); + +/* + * 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! + */ + +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); + +/* + * 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! + */ + +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); + + + +/* + * This is probably overkill for real world processors but + * they are not on hot paths so a little discipline won't do + * any harm. + */ + +static void tty_set_termios_ldisc(struct tty_struct *tty, int num) +{ + down(&tty->termios_sem); + tty->termios->c_line = num; + up(&tty->termios_sem); +} + +/* + * 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); +static struct tty_ldisc tty_ldiscs[NR_LDISCS]; /* line disc dispatch table */ + int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc) { + unsigned long flags; + int ret = 0; + 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)); + spin_lock_irqsave(&tty_ldisc_lock, flags); + tty_ldiscs[disc] = *new_ldisc; + tty_ldiscs[disc].num = disc; + tty_ldiscs[disc].flags |= LDISC_FLAG_DEFINED; + tty_ldiscs[disc].refcount = 0; + spin_unlock_irqrestore(&tty_ldisc_lock, flags); - return 0; + return ret; } - EXPORT_SYMBOL(tty_register_ldisc); -/* Set the discipline of a tty line. */ +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].flags &= ~LDISC_FLAG_DEFINED; + spin_unlock_irqrestore(&tty_ldisc_lock, flags); + + return ret; +} +EXPORT_SYMBOL(tty_unregister_ldisc); + +struct tty_ldisc *tty_ldisc_get(int disc) +{ + unsigned long flags; + struct tty_ldisc *ld; + + if (disc < N_TTY || disc >= NR_LDISCS) + return NULL; + + spin_lock_irqsave(&tty_ldisc_lock, flags); + + ld = &tty_ldiscs[disc]; + /* Check the entry is defined */ + if(ld->flags & LDISC_FLAG_DEFINED) + { + /* If the module is being unloaded we can't use it */ + if (!try_module_get(ld->owner)) + ld = NULL; + else /* lock it */ + ld->refcount++; + } + else + ld = NULL; + spin_unlock_irqrestore(&tty_ldisc_lock, flags); + return ld; +} + +EXPORT_SYMBOL_GPL(tty_ldisc_get); + +void tty_ldisc_put(int disc) +{ + struct tty_ldisc *ld; + unsigned long flags; + + BUG_ON(disc < N_TTY || disc >= NR_LDISCS); + + spin_lock_irqsave(&tty_ldisc_lock, flags); + ld = &tty_ldiscs[disc]; + BUG_ON(ld->refcount == 0); + ld->refcount--; + module_put(ld->owner); + spin_unlock_irqrestore(&tty_ldisc_lock, flags); +} + +EXPORT_SYMBOL_GPL(tty_ldisc_put); + +static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld) +{ + tty->ldisc = *ld; + tty->ldisc.refcount = 0; +} + +/** + * 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 + */ + +static int tty_ldisc_try(struct tty_struct *tty) +{ + unsigned long flags; + struct tty_ldisc *ld; + int ret = 0; + + spin_lock_irqsave(&tty_ldisc_lock, flags); + ld = &tty->ldisc; + if(test_bit(TTY_LDISC, &tty->flags)) + { + ld->refcount++; + ret = 1; + } + spin_unlock_irqrestore(&tty_ldisc_lock, flags); + return ret; +} + +/** + * 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) + */ + +struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty) +{ + /* wait_event is a macro */ + wait_event(tty_ldisc_wait, tty_ldisc_try(tty)); + if(tty->ldisc.refcount == 0) + printk(KERN_ERR "tty_ldisc_ref_wait\n"); + return &tty->ldisc; +} + +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. + */ + +struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty) +{ + if(tty_ldisc_try(tty)) + return &tty->ldisc; + return NULL; +} + +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. + */ + +void tty_ldisc_deref(struct tty_ldisc *ld) +{ + unsigned long flags; + + BUG_ON(ld == NULL); + + spin_lock_irqsave(&tty_ldisc_lock, flags); + if(ld->refcount == 0) + printk(KERN_ERR "tty_ldisc_deref: no references.\n"); + else + ld->refcount--; + if(ld->refcount == 0) + wake_up(&tty_ldisc_wait); + spin_unlock_irqrestore(&tty_ldisc_lock, flags); +} + +EXPORT_SYMBOL_GPL(tty_ldisc_deref); + +/** + * 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 neccessary wakeups for existing sleepers. + * + * Note: nobody should set this bit except via this function. Clearing + * directly is allowed. + */ + +static void tty_ldisc_enable(struct tty_struct *tty) +{ + set_bit(TTY_LDISC, &tty->flags); + wake_up(&tty_ldisc_wait); +} + +/** + * 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. + */ + static int tty_set_ldisc(struct tty_struct *tty, int ldisc) { - int retval = 0; - struct tty_ldisc o_ldisc; + int retval = 0; + struct tty_ldisc o_ldisc; char buf[64]; + int work; + unsigned long flags; + struct tty_ldisc *ld; + struct tty_struct *o_tty; if ((ldisc < N_TTY) || (ldisc >= NR_LDISCS)) return -EINVAL; + +restart: + + ld = tty_ldisc_get(ldisc); /* Eduardo Blanco */ /* Cyrus Durgin */ - if (!(ldiscs[ldisc].flags & LDISC_FLAG_DEFINED)) { + if (ld == NULL) { request_module("tty-ldisc-%d", ldisc); + ld = tty_ldisc_get(ldisc); } - if (!(ldiscs[ldisc].flags & LDISC_FLAG_DEFINED)) + if (ld == NULL) return -EINVAL; - if (tty->ldisc.num == ldisc) - return 0; /* We are already in the desired discipline */ + /* + * No more input please, we are switching. The new ldisc + * will update this value in the ldisc open function + */ + + tty->receive_room = 0; - if (!try_module_get(ldiscs[ldisc].owner)) - return -EINVAL; - - o_ldisc = tty->ldisc; + /* + * Problem: What do we do if this blocks ? + */ tty_wait_until_sent(tty, 0); - + + if (tty->ldisc.num == ldisc) { + tty_ldisc_put(ldisc); + return 0; + } + + o_ldisc = tty->ldisc; + o_tty = tty->link; + + /* + * 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. + */ + + spin_lock_irqsave(&tty_ldisc_lock, flags); + if (tty->ldisc.refcount || (o_tty && o_tty->ldisc.refcount)) { + if(tty->ldisc.refcount) { + /* Free the new ldisc we grabbed. Must drop the lock + first. */ + spin_unlock_irqrestore(&tty_ldisc_lock, flags); + tty_ldisc_put(ldisc); + /* + * There are several reasons we may be busy, including + * random momentary I/O traffic. We must therefore + * retry. We could distinguish between blocking ops + * and retries if we made tty_ldisc_wait() smarter. That + * is up for discussion. + */ + if (wait_event_interruptible(tty_ldisc_wait, tty->ldisc.refcount == 0) < 0) + return -ERESTARTSYS; + goto restart; + } + if(o_tty && o_tty->ldisc.refcount) { + spin_unlock_irqrestore(&tty_ldisc_lock, flags); + tty_ldisc_put(ldisc); + if (wait_event_interruptible(tty_ldisc_wait, o_tty->ldisc.refcount == 0) < 0) + return -ERESTARTSYS; + goto restart; + } + } + + /* if the TTY_LDISC bit is set, then we are racing against another ldisc change */ + + if (!test_bit(TTY_LDISC, &tty->flags)) { + spin_unlock_irqrestore(&tty_ldisc_lock, flags); + tty_ldisc_put(ldisc); + ld = tty_ldisc_ref_wait(tty); + tty_ldisc_deref(ld); + goto restart; + } + + clear_bit(TTY_LDISC, &tty->flags); + clear_bit(TTY_DONT_FLIP, &tty->flags); + if (o_tty) { + clear_bit(TTY_LDISC, &o_tty->flags); + clear_bit(TTY_DONT_FLIP, &o_tty->flags); + } + spin_unlock_irqrestore(&tty_ldisc_lock, flags); + + /* + * From this point on we know nobody has an ldisc + * usage reference, nor can they obtain one until + * we say so later on. + */ + + work = cancel_delayed_work(&tty->buf.work); + /* + * Wait for ->hangup_work and ->buf.work handlers to terminate + */ + + flush_scheduled_work(); /* 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; + tty_ldisc_assign(tty, ld); + tty_set_termios_ldisc(tty, ldisc); if (tty->ldisc.open) retval = (tty->ldisc.open)(tty); if (retval < 0) { - tty->ldisc = o_ldisc; - tty->termios->c_line = tty->ldisc.num; + tty_ldisc_put(ldisc); + /* There is an outstanding reference here so this is safe */ + tty_ldisc_assign(tty, tty_ldisc_get(o_ldisc.num)); + tty_set_termios_ldisc(tty, tty->ldisc.num); if (tty->ldisc.open && (tty->ldisc.open(tty) < 0)) { - tty->ldisc = ldiscs[N_TTY]; - tty->termios->c_line = N_TTY; + tty_ldisc_put(o_ldisc.num); + /* This driver is always present */ + tty_ldisc_assign(tty, tty_ldisc_get(N_TTY)); + tty_set_termios_ldisc(tty, N_TTY); if (tty->ldisc.open) { int r = tty->ldisc.open(tty); @@ -291,19 +832,36 @@ static int tty_set_ldisc(struct tty_struct *tty, int ldisc) tty_name(tty, buf), r); } } - } else { - module_put(o_ldisc.owner); } + /* 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.num != o_ldisc.num && tty->driver->set_ldisc) tty->driver->set_ldisc(tty); + + tty_ldisc_put(o_ldisc.num); + + /* + * Allow ldisc referencing to occur as soon as the driver + * ldisc callback completes. + */ + + tty_ldisc_enable(tty); + if (o_tty) + tty_ldisc_enable(o_tty); + + /* Restart it in case no characters kick it off. Safe if + already running */ + if (work) + schedule_delayed_work(&tty->buf.work, 1); return retval; } /* * This routine returns a tty driver structure, given a device number */ -struct tty_driver *get_tty_driver(dev_t device, int *index) +static struct tty_driver *get_tty_driver(dev_t device, int *index) { struct tty_driver *p; @@ -377,6 +935,19 @@ static struct file_operations tty_fops = { .fasync = tty_fasync, }; +#ifdef CONFIG_UNIX98_PTYS +static struct file_operations ptmx_fops = { + .llseek = no_llseek, + .read = tty_read, + .write = tty_write, + .poll = tty_poll, + .ioctl = tty_ioctl, + .open = ptmx_open, + .release = tty_release, + .fasync = tty_fasync, +}; +#endif + static struct file_operations console_fops = { .llseek = no_llseek, .read = tty_read, @@ -397,20 +968,67 @@ static struct file_operations hung_up_tty_fops = { .release = tty_release, }; -static spinlock_t redirect_lock = SPIN_LOCK_UNLOCKED; +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->write_wakeup) + ld->write_wakeup(tty); + tty_ldisc_deref(ld); + } + } + wake_up_interruptible(&tty->write_wait); +} + +EXPORT_SYMBOL_GPL(tty_wakeup); + +/** + * 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->flush_buffer) + ld->flush_buffer(tty); + tty_ldisc_deref(ld); + } +} + +EXPORT_SYMBOL_GPL(tty_ldisc_flush); + /* * 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.. */ -void do_tty_hangup(void *data) +static void do_tty_hangup(void *data) { struct tty_struct *tty = (struct tty_struct *) data; struct file * cons_filp = NULL; struct file *filp, *f = NULL; struct task_struct *p; - struct pid *pid; + struct tty_ldisc *ld; int closecount = 0, n; if (!tty) @@ -428,7 +1046,8 @@ void do_tty_hangup(void *data) check_tty_count(tty, "do_tty_hangup"); file_list_lock(); - list_for_each_entry(filp, &tty->tty_files, f_list) { + /* This breaks for file handles being sent over AF_UNIX sockets ? */ + list_for_each_entry(filp, &tty->tty_files, f_u.fu_list) { if (filp->f_op->write == redirected_tty_write) cons_filp = filp; if (filp->f_op->write != tty_write) @@ -440,21 +1059,25 @@ void do_tty_hangup(void *data) file_list_unlock(); /* FIXME! What are the locking issues here? This may me overdoing things.. - * this question is especially important now that we've removed the irqlock. */ - { - unsigned long flags; + * this question is especially important now that we've removed the irqlock. */ - local_irq_save(flags); // FIXME: is this safe? - if (tty->ldisc.flush_buffer) - tty->ldisc.flush_buffer(tty); + ld = tty_ldisc_ref(tty); + if(ld != NULL) /* We may have no line discipline at this point */ + { + if (ld->flush_buffer) + ld->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); - local_irq_restore(flags); // FIXME: is this safe? + ld->write_wakeup) + ld->write_wakeup(tty); + if (ld->hangup) + ld->hangup(tty); } + /* FIXME: Once we trust the LDISC code better we can wait here for + ldisc completion and fix the driver call race */ + wake_up_interruptible(&tty->write_wait); wake_up_interruptible(&tty->read_wait); @@ -463,35 +1086,30 @@ void do_tty_hangup(void *data) * N_TTY. */ if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) + { + down(&tty->termios_sem); *tty->termios = tty->driver->init_termios; - if (tty->ldisc.num != ldiscs[N_TTY].num) { - if (tty->ldisc.close) - (tty->ldisc.close)(tty); - module_put(tty->ldisc.owner); - - 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(KERN_ERR "do_tty_hangup: N_TTY open: " - "error %d\n", -i); - } + up(&tty->termios_sem); } + /* Defer ldisc switch */ + /* tty_deferred_ldisc_switch(N_TTY); + + This should get done automatically when the port closes and + tty_release is called */ + read_lock(&tasklist_lock); if (tty->session > 0) { - struct list_head *l; - for_each_task_pid(tty->session, PIDTYPE_SID, p, l, pid) { + do_each_task_pid(tty->session, PIDTYPE_SID, p) { if (p->signal->tty == tty) p->signal->tty = NULL; if (!p->signal->leader) continue; - send_group_sig_info(SIGHUP, SEND_SIG_PRIV, p); - send_group_sig_info(SIGCONT, SEND_SIG_PRIV, p); + group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p); + group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p); if (tty->pgrp > 0) p->signal->tty_old_pgrp = tty->pgrp; - } + } while_each_task_pid(tty->session, PIDTYPE_SID, p); } read_unlock(&tasklist_lock); @@ -511,6 +1129,17 @@ void do_tty_hangup(void *data) tty->driver->close(tty, cons_filp); } else if (tty->driver->hangup) (tty->driver->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); + if (ld) { + tty_ldisc_enable(tty); + tty_ldisc_deref(ld); + } unlock_kernel(); if (f) fput(f); @@ -563,15 +1192,15 @@ void disassociate_ctty(int on_exit) { struct tty_struct *tty; struct task_struct *p; - struct list_head *l; - struct pid *pid; int tty_pgrp = -1; lock_kernel(); + mutex_lock(&tty_mutex); tty = current->signal->tty; if (tty) { tty_pgrp = tty->pgrp; + mutex_unlock(&tty_mutex); if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY) tty_vhangup(tty); } else { @@ -579,6 +1208,7 @@ void disassociate_ctty(int on_exit) kill_pg(current->signal->tty_old_pgrp, SIGHUP, on_exit); kill_pg(current->signal->tty_old_pgrp, SIGCONT, on_exit); } + mutex_unlock(&tty_mutex); unlock_kernel(); return; } @@ -588,14 +1218,19 @@ void disassociate_ctty(int on_exit) kill_pg(tty_pgrp, SIGCONT, on_exit); } + /* Must lock changes to tty_old_pgrp */ + mutex_lock(&tty_mutex); current->signal->tty_old_pgrp = 0; tty->session = 0; tty->pgrp = -1; + /* Now clear signal->tty under the lock */ read_lock(&tasklist_lock); - for_each_task_pid(current->signal->session, PIDTYPE_SID, p, l, pid) + do_each_task_pid(current->signal->session, PIDTYPE_SID, p) { p->signal->tty = NULL; + } while_each_task_pid(current->signal->session, PIDTYPE_SID, p); read_unlock(&tasklist_lock); + mutex_unlock(&tty_mutex); unlock_kernel(); } @@ -627,9 +1262,9 @@ void start_tty(struct tty_struct *tty) } 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); + + /* If we have a running line discipline it may need kicking */ + tty_wakeup(tty); wake_up_interruptible(&tty->write_wait); } @@ -641,6 +1276,7 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count, int i; struct tty_struct * tty; struct inode *inode; + struct tty_ldisc *ld; tty = (struct tty_struct *)file->private_data; inode = file->f_dentry->d_inode; @@ -649,14 +1285,18 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count, 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); lock_kernel(); - if (tty->ldisc.read) - i = (tty->ldisc.read)(tty,file,buf,count); + if (ld->read) + i = (ld->read)(tty,file,buf,count); else i = -EIO; + tty_ldisc_deref(ld); unlock_kernel(); if (i > 0) - inode->i_atime = CURRENT_TIME; + inode->i_atime = current_fs_time(inode->i_sb); return i; } @@ -665,47 +1305,84 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count, * denial-of-service type attacks */ static inline ssize_t do_tty_write( - ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char __user *, size_t), + ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t), struct tty_struct *tty, struct file *file, - const unsigned char __user *buf, + const char __user *buf, size_t count) { ssize_t ret = 0, written = 0; + unsigned int chunk; - if (down_interruptible(&tty->atomic_write)) { + if (mutex_lock_interruptible(&tty->atomic_write_lock)) { return -ERESTARTSYS; } - if ( test_bit(TTY_NO_WRITE_SPLIT, &tty->flags) ) { + + /* + * 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. + */ + 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; + + if (chunk < 1024) + chunk = 1024; + + buf = kmalloc(chunk, GFP_KERNEL); + if (!buf) { + mutex_unlock(&tty->atomic_write_lock); + return -ENOMEM; + } + kfree(tty->write_buf); + tty->write_cnt = chunk; + tty->write_buf = buf; + } + + /* 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; lock_kernel(); - written = write(tty, file, buf, count); + ret = write(tty, file, tty->write_buf, size); unlock_kernel(); - } else { - for (;;) { - unsigned long size = max((unsigned long)PAGE_SIZE*2, 16384UL); - if (size > count) - size = count; - lock_kernel(); - ret = write(tty, file, buf, size); - unlock_kernel(); - if (ret <= 0) - break; - written += ret; - buf += ret; - count -= ret; - if (!count) - break; - ret = -ERESTARTSYS; - if (signal_pending(current)) - break; - cond_resched(); - } + if (ret <= 0) + break; + written += ret; + buf += ret; + count -= ret; + if (!count) + break; + ret = -ERESTARTSYS; + if (signal_pending(current)) + break; + cond_resched(); } if (written) { - file->f_dentry->d_inode->i_mtime = CURRENT_TIME; + struct inode *inode = file->f_dentry->d_inode; + inode->i_mtime = current_fs_time(inode->i_sb); ret = written; } - up(&tty->atomic_write); + mutex_unlock(&tty->atomic_write_lock); return ret; } @@ -715,16 +1392,22 @@ static ssize_t tty_write(struct file * file, const char __user * buf, size_t cou { struct tty_struct * tty; struct inode *inode = file->f_dentry->d_inode; - + ssize_t ret; + struct tty_ldisc *ld; + tty = (struct tty_struct *)file->private_data; if (tty_paranoia_check(tty, inode, "tty_write")) return -EIO; if (!tty || !tty->driver->write || (test_bit(TTY_IO_ERROR, &tty->flags))) return -EIO; - if (!tty->ldisc.write) - return -EIO; - return do_tty_write(tty->ldisc.write, tty, file, - (const unsigned char __user *)buf, count); + + ld = tty_ldisc_ref_wait(tty); + if (!ld->write) + ret = -EIO; + else + ret = do_tty_write(ld->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, @@ -749,6 +1432,17 @@ ssize_t redirected_tty_write(struct file * file, const char __user * buf, size_t return tty_write(file, buf, count, ppos); } +static char ptychar[] = "pqrstuvwxyzabcde"; + +static inline 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); +} + static inline void tty_line_name(struct tty_driver *driver, int index, char *p) { sprintf(p, "%s%d", driver->name, index + driver->name_base); @@ -756,8 +1450,8 @@ static inline void tty_line_name(struct tty_driver *driver, int index, char *p) /* * 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 + * 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. */ static int init_dev(struct tty_driver *driver, int idx, @@ -768,12 +1462,6 @@ static int init_dev(struct tty_driver *driver, int idx, struct termios *ltp, **ltp_loc, *o_ltp, **o_ltp_loc; int retval=0; - /* - * Check whether we need to acquire the tty semaphore to avoid - * race conditions. For now, play it safe. - */ - down(&tty_sem); - /* check whether we're reopening an existing tty */ if (driver->flags & TTY_DRIVER_DEVPTS_MEM) { tty = devpts_get_tty(idx); @@ -910,6 +1598,7 @@ static int init_dev(struct tty_driver *driver, int idx, * 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) @@ -922,7 +1611,9 @@ static int init_dev(struct tty_driver *driver, int idx, (tty->ldisc.close)(tty); goto release_mem_out; } + tty_ldisc_enable(o_tty); } + tty_ldisc_enable(tty); goto success; /* @@ -951,24 +1642,23 @@ fast_track: tty->count++; tty->driver = driver; /* N.B. why do this every time?? */ + /* FIXME */ + if(!test_bit(TTY_LDISC, &tty->flags)) + printk(KERN_ERR "init_dev but no ldisc\n"); success: *ret_tty = tty; - /* All paths come through here to release the semaphore */ + /* All paths come through here to release the mutex */ end_init: - up(&tty_sem); return retval; /* Release locally allocated memory ... nothing placed in slots */ free_mem_out: - if (o_tp) - kfree(o_tp); + kfree(o_tp); if (o_tty) free_tty_struct(o_tty); - if (ltp) - kfree(ltp); - if (tp) - kfree(tp); + kfree(ltp); + kfree(tp); free_tty_struct(tty); fail_no_mem: @@ -984,6 +1674,19 @@ release_mem_out: goto end_init; } +/* + * Get a copy of the termios structure for the driver/index + */ +void tty_get_termios(struct tty_driver *driver, int idx, struct termios *tio) +{ + lock_kernel(); + if (driver->termios[idx]) + *tio = *driver->termios[idx]; + else + *tio = driver->init_termios; + unlock_kernel(); +} + /* * Releases memory associated with a tty structure, and clears out the * driver table slots. @@ -1051,9 +1754,10 @@ static void release_dev(struct file * filp) { struct tty_struct *tty, *o_tty; int pty_master, tty_closing, o_tty_closing, do_sleep; - int devpts_master, devpts; + int devpts; int idx; char buf[64]; + unsigned long flags; tty = (struct tty_struct *)filp->private_data; if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "release_dev")) @@ -1067,7 +1771,6 @@ static void release_dev(struct file * filp) pty_master = (tty->driver->type == TTY_DRIVER_TYPE_PTY && tty->driver->subtype == PTY_TYPE_MASTER); devpts = (tty->driver->flags & TTY_DRIVER_DEVPTS_MEM) != 0; - devpts_master = pty_master && devpts; o_tty = tty->link; #ifdef TTY_PARANOIA_CHECK @@ -1130,7 +1833,6 @@ static void release_dev(struct file * filp) } } #endif - if (tty->driver->close) tty->driver->close(tty, filp); @@ -1152,6 +1854,10 @@ static void release_dev(struct file * filp) * 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_closing = tty->count <= 1; o_tty_closing = o_tty && (o_tty->count <= (pty_master ? 1 : 0)); @@ -1182,6 +1888,7 @@ static void release_dev(struct file * filp) printk(KERN_WARNING "release_dev: %s: read/write wait queue " "active!\n", tty_name(tty, buf)); + mutex_unlock(&tty_mutex); schedule(); } @@ -1203,7 +1910,7 @@ static void release_dev(struct file * filp) 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 @@ -1235,18 +1942,20 @@ static void release_dev(struct file * filp) */ if (tty_closing || o_tty_closing) { struct task_struct *p; - struct list_head *l; - struct pid *pid; read_lock(&tasklist_lock); - for_each_task_pid(tty->session, PIDTYPE_SID, p, l, pid) + do_each_task_pid(tty->session, PIDTYPE_SID, p) { p->signal->tty = NULL; + } while_each_task_pid(tty->session, PIDTYPE_SID, p); if (o_tty) - for_each_task_pid(o_tty->session, PIDTYPE_SID, p,l, pid) + do_each_task_pid(o_tty->session, PIDTYPE_SID, p) { p->signal->tty = NULL; + } while_each_task_pid(o_tty->session, PIDTYPE_SID, p); read_unlock(&tasklist_lock); } + mutex_unlock(&tty_mutex); + /* check whether both sides are closing ... */ if (!tty_closing || (o_tty && !o_tty_closing)) return; @@ -1254,36 +1963,58 @@ static void release_dev(struct file * filp) #ifdef TTY_DEBUG_HANGUP printk(KERN_DEBUG "freeing tty structure..."); #endif - /* * Prevent flush_to_ldisc() from rescheduling the work for later. Then - * kill any delayed work. + * kill any delayed work. As this is the final close it does not + * race with the set_ldisc code path. */ + clear_bit(TTY_LDISC, &tty->flags); clear_bit(TTY_DONT_FLIP, &tty->flags); - cancel_delayed_work(&tty->flip.work); + cancel_delayed_work(&tty->buf.work); /* - * Wait for ->hangup_work and ->flip.work handlers to terminate + * Wait for ->hangup_work and ->buf.work handlers to terminate */ + flush_scheduled_work(); - + + /* + * Wait for any short term users (we know they are just driver + * side waiters as the file is closing so user count on the file + * side is zero. + */ + spin_lock_irqsave(&tty_ldisc_lock, flags); + while(tty->ldisc.refcount) + { + spin_unlock_irqrestore(&tty_ldisc_lock, flags); + wait_event(tty_ldisc_wait, tty->ldisc.refcount == 0); + spin_lock_irqsave(&tty_ldisc_lock, flags); + } + spin_unlock_irqrestore(&tty_ldisc_lock, flags); /* * Shutdown the current line discipline, and reset it to N_TTY. * N.B. why reset ldisc when we're releasing the memory?? + * + * FIXME: this MUST get fixed for the new reflocking */ if (tty->ldisc.close) (tty->ldisc.close)(tty); - module_put(tty->ldisc.owner); + tty_ldisc_put(tty->ldisc.num); - tty->ldisc = ldiscs[N_TTY]; - tty->termios->c_line = N_TTY; + /* + * Switch the line discipline back + */ + tty_ldisc_assign(tty, tty_ldisc_get(N_TTY)); + tty_set_termios_ldisc(tty,N_TTY); if (o_tty) { + /* FIXME: could o_tty be in setldisc here ? */ + clear_bit(TTY_LDISC, &o_tty->flags); if (o_tty->ldisc.close) (o_tty->ldisc.close)(o_tty); - module_put(o_tty->ldisc.owner); - o_tty->ldisc = ldiscs[N_TTY]; + tty_ldisc_put(o_tty->ldisc.num); + tty_ldisc_assign(o_tty, tty_ldisc_get(N_TTY)); + tty_set_termios_ldisc(o_tty,N_TTY); } - /* * The release_mem function takes care of the details of clearing * the slots and preserving the termios structure. @@ -1323,14 +2054,19 @@ static int tty_open(struct inode * inode, struct file * filp) unsigned short 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); if (device == MKDEV(TTYAUX_MAJOR,0)) { - if (!current->signal->tty) + if (!current->signal->tty) { + mutex_unlock(&tty_mutex); return -ENXIO; + } driver = current->signal->tty->driver; index = current->signal->tty->index; filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */ @@ -1339,7 +2075,6 @@ retry_open: } #ifdef CONFIG_VT if (device == MKDEV(TTY_MAJOR,0)) { - extern int fg_console; extern struct tty_driver *console_driver; driver = console_driver; index = fg_console; @@ -1355,56 +2090,20 @@ retry_open: noctty = 1; goto got_driver; } + mutex_unlock(&tty_mutex); return -ENODEV; } -#ifdef CONFIG_UNIX98_PTYS - if (device == MKDEV(TTYAUX_MAJOR,2)) { - int idr_ret; - - /* find a device that is not in use. */ - down(&allocated_ptys_lock); - if (!idr_pre_get(&allocated_ptys, GFP_KERNEL)) { - up(&allocated_ptys_lock); - return -ENOMEM; - } - idr_ret = idr_get_new(&allocated_ptys, NULL, &index); - if (idr_ret < 0) { - up(&allocated_ptys_lock); - if (idr_ret == -EAGAIN) - return -ENOMEM; - return -EIO; - } - if (index >= pty_limit) { - idr_remove(&allocated_ptys, index); - up(&allocated_ptys_lock); - return -EIO; - } - up(&allocated_ptys_lock); - - driver = ptm_driver; - retval = init_dev(driver, index, &tty); - if (retval) { - down(&allocated_ptys_lock); - idr_remove(&allocated_ptys, index); - up(&allocated_ptys_lock); - return retval; - } - - set_bit(TTY_PTY_LOCK, &tty->flags); /* LOCK THE SLAVE */ - if (devpts_pty_new(tty->link)) - retval = -ENOMEM; - } else -#endif - { - driver = get_tty_driver(device, &index); - if (!driver) - return -ENODEV; -got_driver: - retval = init_dev(driver, index, &tty); - if (retval) - return retval; + driver = get_tty_driver(device, &index); + if (!driver) { + mutex_unlock(&tty_mutex); + return -ENODEV; } +got_driver: + retval = init_dev(driver, index, &tty); + mutex_unlock(&tty_mutex); + if (retval) + return retval; filp->private_data = tty; file_move(filp, &tty->tty_files); @@ -1431,15 +2130,6 @@ got_driver: printk(KERN_DEBUG "error %d in opening %s...", retval, tty->name); #endif - -#ifdef CONFIG_UNIX98_PTYS - if (index != -1) { - down(&allocated_ptys_lock); - idr_remove(&allocated_ptys, index); - up(&allocated_ptys_lock); - } -#endif - release_dev(filp); if (retval != -ERESTARTSYS) return retval; @@ -1467,6 +2157,66 @@ got_driver: return 0; } +#ifdef CONFIG_UNIX98_PTYS +static int ptmx_open(struct inode * inode, struct file * filp) +{ + struct tty_struct *tty; + int retval; + int index; + int idr_ret; + + nonseekable_open(inode, filp); + + /* find a device that is not in use. */ + down(&allocated_ptys_lock); + if (!idr_pre_get(&allocated_ptys, GFP_KERNEL)) { + up(&allocated_ptys_lock); + return -ENOMEM; + } + idr_ret = idr_get_new(&allocated_ptys, NULL, &index); + if (idr_ret < 0) { + up(&allocated_ptys_lock); + if (idr_ret == -EAGAIN) + return -ENOMEM; + return -EIO; + } + if (index >= pty_limit) { + idr_remove(&allocated_ptys, index); + up(&allocated_ptys_lock); + return -EIO; + } + up(&allocated_ptys_lock); + + mutex_lock(&tty_mutex); + retval = init_dev(ptm_driver, index, &tty); + mutex_unlock(&tty_mutex); + + if (retval) + goto out; + + set_bit(TTY_PTY_LOCK, &tty->flags); /* LOCK THE SLAVE */ + filp->private_data = tty; + file_move(filp, &tty->tty_files); + + retval = -ENOMEM; + if (devpts_pty_new(tty->link)) + goto out1; + + check_tty_count(tty, "tty_open"); + retval = ptm_driver->open(tty, filp); + if (!retval) + return 0; +out1: + release_dev(filp); + return retval; +out: + down(&allocated_ptys_lock); + idr_remove(&allocated_ptys, index); + up(&allocated_ptys_lock); + return retval; +} +#endif + static int tty_release(struct inode * inode, struct file * filp) { lock_kernel(); @@ -1479,14 +2229,18 @@ static int tty_release(struct inode * inode, struct file * filp) static unsigned int tty_poll(struct file * filp, poll_table * wait) { struct tty_struct * tty; + struct tty_ldisc *ld; + int ret = 0; tty = (struct tty_struct *)filp->private_data; if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "tty_poll")) return 0; - - if (tty->ldisc.poll) - return (tty->ldisc.poll)(tty, filp, wait); - return 0; + + ld = tty_ldisc_ref_wait(tty); + if (ld->poll) + ret = (ld->poll)(tty, filp, wait); + tty_ldisc_deref(ld); + return ret; } static int tty_fasync(int fd, struct file * filp, int on) @@ -1518,12 +2272,15 @@ static int tty_fasync(int fd, struct file * filp, int on) 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->ldisc.receive_buf(tty, &ch, &mbz, 1); + ld = tty_ldisc_ref_wait(tty); + ld->receive_buf(tty, &ch, &mbz, 1); + tty_ldisc_deref(ld); return 0; } @@ -1545,11 +2302,10 @@ static int tiocswinsz(struct tty_struct *tty, struct tty_struct *real_tty, return 0; #ifdef CONFIG_VT if (tty->driver->type == TTY_DRIVER_TYPE_CONSOLE) { - unsigned int currcons = tty->index; int rc; acquire_console_sem(); - rc = vc_resize(currcons, tmp_ws.ws_col, tmp_ws.ws_row); + rc = vc_resize(tty->driver_data, tmp_ws.ws_col, tmp_ws.ws_row); release_console_sem(); if (rc) return -ENXIO; @@ -1566,10 +2322,10 @@ static int tiocswinsz(struct tty_struct *tty, struct tty_struct *real_tty, static int tioccons(struct file *file) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; if (file->f_op->write == redirected_tty_write) { struct file *f; - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; spin_lock(&redirect_lock); f = redirect; redirect = NULL; @@ -1606,8 +2362,6 @@ static int fionbio(struct file *file, int __user *p) static int tiocsctty(struct tty_struct *tty, int arg) { - struct list_head *l; - struct pid *pid; task_t *p; if (current->signal->leader && @@ -1630,8 +2384,9 @@ static int tiocsctty(struct tty_struct *tty, int arg) */ read_lock(&tasklist_lock); - for_each_task_pid(tty->session, PIDTYPE_SID, p, l, pid) + do_each_task_pid(tty->session, PIDTYPE_SID, p) { p->signal->tty = NULL; + } while_each_task_pid(tty->session, PIDTYPE_SID, p); read_unlock(&tasklist_lock); } else return -EPERM; @@ -1647,13 +2402,16 @@ static int tiocsctty(struct tty_struct *tty, int arg) static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) { + pid_t pgrp; /* * (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; - return put_user(real_tty->pgrp, p); + + pgrp = vx_map_pid(real_tty->pgrp); + return put_user(pgrp, p); } static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) @@ -1671,6 +2429,8 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t return -ENOTTY; if (get_user(pgrp, p)) return -EFAULT; + + pgrp = vx_rmap_pid(pgrp); if (pgrp < 0) return -EINVAL; if (session_of_pgrp(pgrp) != current->signal->session) @@ -1701,13 +2461,12 @@ static int tiocsetd(struct tty_struct *tty, int __user *p) return tty_set_ldisc(tty, ldisc); } -static int send_break(struct tty_struct *tty, int duration) +static int send_break(struct tty_struct *tty, unsigned int duration) { - set_current_state(TASK_INTERRUPTIBLE); - tty->driver->break_ctl(tty, -1); - if (!signal_pending(current)) - schedule_timeout(duration); + if (!signal_pending(current)) { + msleep_interruptible(duration); + } tty->driver->break_ctl(tty, 0); if (signal_pending(current)) return -EINTR; @@ -1772,6 +2531,7 @@ int tty_ioctl(struct inode * inode, struct file * file, struct tty_struct *tty, *real_tty; void __user *p = (void __user *)arg; int retval; + struct tty_ldisc *ld; tty = (struct tty_struct *)file->private_data; if (tty_paranoia_check(tty, inode, "tty_ioctl")) @@ -1861,6 +2621,7 @@ int tty_ioctl(struct inode * inode, struct file * file, case TIOCGSID: return tiocgsid(tty, real_tty, p); case TIOCGETD: + /* FIXME: check this is ok */ return put_user(tty->ldisc.num, (int __user *)p); case TIOCSETD: return tiocsetd(tty, p); @@ -1885,10 +2646,10 @@ int tty_ioctl(struct inode * inode, struct file * file, * all by anyone? */ if (!arg) - return send_break(tty, HZ/4); + return send_break(tty, 250); return 0; case TCSBRKP: /* support for POSIX tcsendbreak() */ - return send_break(tty, arg ? arg*(HZ/10) : HZ/4); + return send_break(tty, arg ? arg*100 : 250); case TIOCMGET: return tty_tiocmget(tty, file, p); @@ -1899,16 +2660,19 @@ int tty_ioctl(struct inode * inode, struct file * file, return tty_tiocmset(tty, file, cmd, p); } if (tty->driver->ioctl) { - int retval = (tty->driver->ioctl)(tty, file, cmd, arg); + 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; + ld = tty_ldisc_ref_wait(tty); + retval = -EINVAL; + if (ld->ioctl) { + retval = ld->ioctl(tty, file, cmd, arg); + if (retval == -ENOIOCTLCMD) + retval = -EINVAL; } - return -EINVAL; + tty_ldisc_deref(ld); + return retval; } @@ -1937,23 +2701,39 @@ static void __do_SAK(void *arg) tty_hangup(tty); #else struct tty_struct *tty = arg; - struct task_struct *p; - struct list_head *l; - struct pid *pid; + struct task_struct *g, *p; int session; int i; struct file *filp; + struct tty_ldisc *disc; + struct fdtable *fdt; if (!tty) return; session = tty->session; - if (tty->ldisc.flush_buffer) - tty->ldisc.flush_buffer(tty); + + /* We don't want an ldisc switch during this */ + disc = tty_ldisc_ref(tty); + if (disc && disc->flush_buffer) + disc->flush_buffer(tty); + tty_ldisc_deref(disc); + if (tty->driver->flush_buffer) tty->driver->flush_buffer(tty); + read_lock(&tasklist_lock); - for_each_task_pid(session, PIDTYPE_SID, p, l, pid) { - if (p->signal->tty == tty || session > 0) { + /* Kill the entire session */ + do_each_task_pid(session, PIDTYPE_SID, p) { + printk(KERN_NOTICE "SAK: killed process %d" + " (%s): p->signal->session==tty->session\n", + p->pid, p->comm); + send_sig(SIGKILL, p, 1); + } while_each_task_pid(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): p->signal->session==tty->session\n", p->pid, p->comm); @@ -1962,8 +2742,13 @@ static void __do_SAK(void *arg) } 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); - for (i=0; i < p->files->max_fds; i++) { + fdt = files_fdtable(p->files); + for (i=0; i < fdt->max_fds; i++) { filp = fcheck_files(p->files, i); if (!filp) continue; @@ -1972,14 +2757,14 @@ static void __do_SAK(void *arg) printk(KERN_NOTICE "SAK: killed process %d" " (%s): fd#%d opened to the tty\n", p->pid, p->comm, i); - send_sig(SIGKILL, p, 1); + force_sig(SIGKILL, p); break; } } spin_unlock(&p->files->file_lock); } task_unlock(p); - } + } while_each_thread(g, p); read_unlock(&tasklist_lock); #endif } @@ -2002,43 +2787,50 @@ EXPORT_SYMBOL(do_SAK); /* * This routine is called out of the software interrupt to flush data - * from the flip buffer to the line discipline. + * from the buffer chain 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; + unsigned long flags; + struct tty_ldisc *disc; + struct tty_buffer *tbuf; + int count; + char *char_buf; + unsigned char *flag_buf; + + disc = tty_ldisc_ref(tty); + if (disc == NULL) /* !TTY_LDISC */ + return; if (test_bit(TTY_DONT_FLIP, &tty->flags)) { /* * Do it after the next timer tick: */ - schedule_delayed_work(&tty->flip.work, 1); - return; - } - - spin_lock_irqsave(&tty->read_lock, 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; - 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; - tty->flip.char_buf_ptr = tty->flip.char_buf + TTY_FLIPBUF_SIZE; - tty->flip.flag_buf_ptr = tty->flip.flag_buf + TTY_FLIPBUF_SIZE; + schedule_delayed_work(&tty->buf.work, 1); + goto out; + } + spin_lock_irqsave(&tty->buf.lock, flags); + while((tbuf = tty->buf.head) != NULL) { + while ((count = tbuf->commit - tbuf->read) != 0) { + char_buf = tbuf->char_buf_ptr + tbuf->read; + flag_buf = tbuf->flag_buf_ptr + tbuf->read; + tbuf->read += count; + spin_unlock_irqrestore(&tty->buf.lock, flags); + disc->receive_buf(tty, char_buf, flag_buf, count); + spin_lock_irqsave(&tty->buf.lock, flags); + } + if (tbuf->active) + break; + tty->buf.head = tbuf->next; + if (tty->buf.head == NULL) + tty->buf.tail = NULL; + tty_buffer_free(tty, tbuf); } - count = tty->flip.count; - tty->flip.count = 0; - spin_unlock_irqrestore(&tty->read_lock, flags); - - tty->ldisc.receive_buf(tty, cp, fp, count); + spin_unlock_irqrestore(&tty->buf.lock, flags); +out: + tty_ldisc_deref(disc); } /* @@ -2060,9 +2852,20 @@ static int baud_table[] = { 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. + */ + int tty_termios_baud_rate(struct termios *termios) { - unsigned int cbaud = termios->c_cflag & CBAUD; + unsigned int cbaud; + + cbaud = termios->c_cflag & CBAUD; if (cbaud & CBAUDEX) { cbaud &= ~CBAUDEX; @@ -2072,12 +2875,20 @@ int tty_termios_baud_rate(struct termios *termios) else cbaud += 15; } - return baud_table[cbaud]; } EXPORT_SYMBOL(tty_termios_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. + */ + int tty_get_baud_rate(struct tty_struct *tty) { int baud = tty_termios_baud_rate(tty->termios); @@ -2096,16 +2907,36 @@ int tty_get_baud_rate(struct tty_struct *tty) EXPORT_SYMBOL(tty_get_baud_rate); +/** + * 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. + */ + 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->active = 0; + tty->buf.tail->commit = tty->buf.tail->used; + } + spin_unlock_irqrestore(&tty->buf.lock, flags); + if (tty->low_latency) flush_to_ldisc((void *) tty); else - schedule_delayed_work(&tty->flip.work, 1); + schedule_delayed_work(&tty->buf.work, 1); } EXPORT_SYMBOL(tty_flip_buffer_push); + /* * This subroutine initializes a tty structure. */ @@ -2113,17 +2944,19 @@ 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_ldisc_assign(tty, tty_ldisc_get(N_TTY)); tty->pgrp = -1; - tty->flip.char_buf_ptr = tty->flip.char_buf; - tty->flip.flag_buf_ptr = tty->flip.flag_buf; - INIT_WORK(&tty->flip.work, flush_to_ldisc, tty); - init_MUTEX(&tty->flip.pty_sem); + tty->overrun_time = jiffies; + tty->buf.head = tty->buf.tail = NULL; + tty_buffer_init(tty); + INIT_WORK(&tty->buf.work, flush_to_ldisc, tty); + init_MUTEX(&tty->buf.pty_sem); + init_MUTEX(&tty->termios_sem); init_waitqueue_head(&tty->write_wait); init_waitqueue_head(&tty->read_wait); INIT_WORK(&tty->hangup_work, do_tty_hangup, tty); - sema_init(&tty->atomic_read, 1); - sema_init(&tty->atomic_write, 1); + mutex_init(&tty->atomic_read_lock); + mutex_init(&tty->atomic_write_lock); spin_lock_init(&tty->read_lock); INIT_LIST_HEAD(&tty->tty_files); INIT_WORK(&tty->SAK_work, NULL, NULL); @@ -2134,10 +2967,10 @@ static void initialize_tty_struct(struct tty_struct *tty) */ static void tty_default_put_char(struct tty_struct *tty, unsigned char ch) { - tty->driver->write(tty, 0, &ch, 1); + tty->driver->write(tty, &ch, 1); } -static struct class_simple *tty_class; +static struct class *tty_class; /** * tty_register_device - register a tty device @@ -2154,6 +2987,7 @@ static struct class_simple *tty_class; void 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) { @@ -2165,13 +2999,11 @@ void tty_register_device(struct tty_driver *driver, unsigned index, devfs_mk_cdev(dev, S_IFCHR | S_IRUSR | S_IWUSR, "%s%d", driver->devfs_name, index + driver->name_base); - /* we don't care about the ptys */ - /* how nice to hide this behind some crappy interface.. */ - if (driver->type != TTY_DRIVER_TYPE_PTY) { - char name[64]; + if (driver->type == TTY_DRIVER_TYPE_PTY) + pty_line_name(driver, index, name); + else tty_line_name(driver, index, name); - class_simple_device_add(tty_class, dev, device, name); - } + class_device_create(tty_class, NULL, dev, device, "%s", name); } /** @@ -2185,7 +3017,7 @@ void tty_register_device(struct tty_driver *driver, unsigned index, void tty_unregister_device(struct tty_driver *driver, unsigned index) { devfs_remove("%s%d", driver->devfs_name, index + driver->name_base); - class_simple_device_remove(MKDEV(driver->major, driver->minor_start) + index); + class_device_destroy(tty_class, MKDEV(driver->major, driver->minor_start) + index); } EXPORT_SYMBOL(tty_register_device); @@ -2384,13 +3216,8 @@ void __init console_init(void) #ifdef CONFIG_EARLY_PRINTK disable_early_printk(); #endif -#ifdef CONFIG_SERIAL_68360 - /* This is not a console initcall. I know not what it's doing here. - So I haven't moved it. dwmw2 */ - rs_360_init(); -#endif - call = &__con_initcall_start; - while (call < &__con_initcall_end) { + call = __con_initcall_start; + while (call < __con_initcall_end) { (*call)(); call++; } @@ -2402,7 +3229,7 @@ extern int vty_init(void); static int __init tty_class_init(void) { - tty_class = class_simple_create(THIS_MODULE, "tty"); + tty_class = class_create(THIS_MODULE, "tty"); if (IS_ERR(tty_class)) return PTR_ERR(tty_class); return 0; @@ -2431,22 +3258,22 @@ static int __init tty_init(void) register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0) panic("Couldn't register /dev/tty driver\n"); devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 0), S_IFCHR|S_IRUGO|S_IWUGO, "tty"); - class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty"); + class_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"); devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 1), S_IFCHR|S_IRUSR|S_IWUSR, "console"); - class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 1), NULL, "console"); + class_device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL, "console"); #ifdef CONFIG_UNIX98_PTYS - cdev_init(&ptmx_cdev, &tty_fops); + 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"); devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 2), S_IFCHR|S_IRUGO|S_IWUGO, "ptmx"); - class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx"); + class_device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx"); #endif #ifdef CONFIG_VT @@ -2455,7 +3282,7 @@ static int __init tty_init(void) register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0) panic("Couldn't register /dev/tty0 driver\n"); devfs_mk_cdev(MKDEV(TTY_MAJOR, 0), S_IFCHR|S_IRUSR|S_IWUSR, "vc/0"); - class_simple_device_add(tty_class, MKDEV(TTY_MAJOR, 0), NULL, "tty0"); + class_device_create(tty_class, NULL, MKDEV(TTY_MAJOR, 0), NULL, "tty0"); vty_init(); #endif