]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/char/tty_io.c
[PATCH] new tty buffering access fix
[linux-2.6-omap-h63xx.git] / drivers / char / tty_io.c
index f5649a3377431faba359c87af568193cc9735cd4..eb8b5be4e2494c318e9f4af4195d8cd0b63cede7 100644 (file)
@@ -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,200 @@ 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)
+{
+       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->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;
+                       /* 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 = tty->buf.tail, *n;
+       int left = 0;
+
+       /* 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 != NULL)
+               left = b->size - b->used;
+       if(left >= size)
+               return size;
+       /* This is the slow path - looking for new buffers to use */
+       n = tty_buffer_find(tty, size);
+       if(n == NULL)
+               return left;
+       if(b != NULL)
+               b->next = n;
+       else
+               tty->buf.head = n;
+       tty->buf.tail = n;
+       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);
+       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);
+       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 
@@ -492,6 +689,17 @@ restart:
        if (ld == NULL)
                return -EINVAL;
 
+       /*
+        *      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) {
@@ -560,9 +768,9 @@ restart:
         *      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();
@@ -616,7 +824,7 @@ restart:
        /* Restart it in case no characters kick it off. Safe if
           already running */
        if (work)
-               schedule_delayed_work(&tty->flip.work, 1);
+               schedule_delayed_work(&tty->buf.work, 1);
        return retval;
 }
 
@@ -809,7 +1017,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)
@@ -1416,14 +1624,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:
@@ -1724,10 +1929,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();
@@ -2521,17 +2726,15 @@ 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;
 
        disc = tty_ldisc_ref(tty);
        if (disc == NULL)       /*  !TTY_LDISC */
@@ -2541,28 +2744,23 @@ 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;
-       }
-       count = tty->flip.count;
-       tty->flip.count = 0;
+       while((tbuf = tty->buf.head) != NULL) {
+               tty->buf.head = tbuf->next;
+               if (tty->buf.head == NULL)
+                       tty->buf.tail = NULL;
+               spin_unlock_irqrestore(&tty->read_lock, flags);
+               /* printk("Process buffer %p for %d\n", tbuf, tbuf->used); */
+               disc->receive_buf(tty, tbuf->char_buf_ptr,
+                                      tbuf->flag_buf_ptr,
+                                      tbuf->used);
+               spin_lock_irqsave(&tty->read_lock, flags);
+               tty_buffer_free(tty, tbuf);
+       }
        spin_unlock_irqrestore(&tty->read_lock, flags);
-
-       disc->receive_buf(tty, cp, fp, count);
 out:
        tty_ldisc_deref(disc);
 }
@@ -2657,11 +2855,12 @@ void tty_flip_buffer_push(struct tty_struct *tty)
        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.
  */
@@ -2672,10 +2871,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);