X-Git-Url: http://pilppa.org/gitweb/gitweb.cgi?a=blobdiff_plain;f=drivers%2Fchar%2Ftty_io.c;h=53d3d066554e556b4110398990294a12af9d1c97;hb=5c8f858d71054960f08c46703a3f0fb36a752079;hp=9d657127f313f21e653b0dd7b49fc539a66a0501;hpb=f5c7f03113fc9c547012cf403aec4b534d575ef0;p=linux-2.6-omap-h63xx.git diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index 9d657127f31..53d3d066554 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c @@ -166,9 +166,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); } @@ -230,6 +233,222 @@ 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, 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; +/* printk("Flip insert %d.\n", 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_GPL(tty_insert_flip_string); + +int tty_insert_flip_string_flags(struct tty_struct *tty, unsigned char *chars, 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_GPL(tty_insert_flip_string_flags); + + +/* + * 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 @@ -469,21 +688,19 @@ static void tty_ldisc_enable(struct tty_struct *tty) 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: - if (tty->ldisc.num == ldisc) - return 0; /* We are already in the desired discipline */ - ld = tty_ldisc_get(ldisc); /* Eduardo Blanco */ /* Cyrus Durgin */ @@ -494,48 +711,88 @@ restart: if (ld == NULL) return -EINVAL; - o_ldisc = tty->ldisc; + /* + * No more input please, we are switching. The new ldisc + * will update this value in the ldisc open function + */ + + tty->receive_room = 0; + + /* + * 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) - { - /* Free the new ldisc we grabbed. Must drop the lock - first. */ + 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); - /* - * 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; + ld = tty_ldisc_ref_wait(tty); + tty_ldisc_deref(ld); goto restart; } - clear_bit(TTY_LDISC, &tty->flags); + + 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->flip.work); + + 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(); @@ -583,11 +840,13 @@ restart: */ 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->flip.work, 1); + if (work) + schedule_delayed_work(&tty->buf.work, 1); return retval; } @@ -780,7 +1039,7 @@ static void do_tty_hangup(void *data) check_tty_count(tty, "do_tty_hangup"); file_list_lock(); /* This breaks for file handles being sent over AF_UNIX sockets ? */ - list_for_each_entry(filp, &tty->tty_files, f_list) { + 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) @@ -1387,14 +1646,11 @@ end_init: /* 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: @@ -1585,7 +1841,6 @@ static void release_dev(struct file * filp) tty_closing = tty->count <= 1; o_tty_closing = o_tty && (o_tty->count <= (pty_master ? 1 : 0)); - up(&tty_sem); do_sleep = 0; if (tty_closing) { @@ -1613,6 +1868,7 @@ static void release_dev(struct file * filp) printk(KERN_WARNING "release_dev: %s: read/write wait queue " "active!\n", tty_name(tty, buf)); + up(&tty_sem); schedule(); } @@ -1621,8 +1877,6 @@ static void release_dev(struct file * filp) * both sides, and we've completed the last operation that could * block, so it's safe to proceed with closing. */ - - down(&tty_sem); if (pty_master) { if (--o_tty->count < 0) { printk(KERN_WARNING "release_dev: bad pty slave count " @@ -1636,7 +1890,6 @@ static void release_dev(struct file * filp) tty->count, tty_name(tty, buf)); tty->count = 0; } - up(&tty_sem); /* * We've decremented tty->count, so we need to remove this file @@ -1681,6 +1934,8 @@ static void release_dev(struct file * filp) read_unlock(&tasklist_lock); } + up(&tty_sem); + /* check whether both sides are closing ... */ if (!tty_closing || (o_tty && !o_tty_closing)) return; @@ -1695,10 +1950,10 @@ static void release_dev(struct file * filp) */ 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(); @@ -2425,6 +2680,7 @@ static void __do_SAK(void *arg) int i; struct file *filp; struct tty_ldisc *disc; + struct fdtable *fdt; if (!tty) return; @@ -2450,8 +2706,9 @@ static void __do_SAK(void *arg) } task_lock(p); if (p->files) { - spin_lock(&p->files->file_lock); - for (i=0; i < p->files->max_fds; i++) { + rcu_read_lock(); + fdt = files_fdtable(p->files); + for (i=0; i < fdt->max_fds; i++) { filp = fcheck_files(p->files, i); if (!filp) continue; @@ -2464,7 +2721,7 @@ static void __do_SAK(void *arg) break; } } - spin_unlock(&p->files->file_lock); + rcu_read_unlock(); } task_unlock(p); } while_each_task_pid(session, PIDTYPE_SID, p); @@ -2490,17 +2747,18 @@ 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; 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 */ @@ -2510,28 +2768,27 @@ static void flush_to_ldisc(void *private_) /* * Do it after the next timer tick: */ - schedule_delayed_work(&tty->flip.work, 1); + schedule_delayed_work(&tty->buf.work, 1); goto out; } - 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; + 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); - - disc->receive_buf(tty, cp, fp, count); + spin_unlock_irqrestore(&tty->buf.lock, flags); out: tty_ldisc_deref(disc); } @@ -2623,14 +2880,23 @@ EXPORT_SYMBOL(tty_get_baud_rate); 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. */ @@ -2641,10 +2907,10 @@ static void initialize_tty_struct(struct tty_struct *tty) tty_ldisc_assign(tty, tty_ldisc_get(N_TTY)); tty->pgrp = -1; tty->overrun_time = jiffies; - 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->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); @@ -2697,7 +2963,7 @@ void tty_register_device(struct tty_driver *driver, unsigned index, pty_line_name(driver, index, name); else tty_line_name(driver, index, name); - class_device_create(tty_class, dev, device, name); + class_device_create(tty_class, NULL, dev, device, "%s", name); } /** @@ -2952,14 +3218,14 @@ 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_device_create(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_device_create(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, &ptmx_fops); @@ -2967,7 +3233,7 @@ static int __init tty_init(void) 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_device_create(tty_class, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx"); + class_device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx"); #endif #ifdef CONFIG_VT @@ -2976,7 +3242,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_device_create(tty_class, MKDEV(TTY_MAJOR, 0), NULL, "tty0"); + class_device_create(tty_class, NULL, MKDEV(TTY_MAJOR, 0), NULL, "tty0"); vty_init(); #endif