+/*
+ * 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);
+
+
+