Serial support

Index: linux-2.6.22-source/drivers/char/coserial.c
===================================================================
--- /dev/null
+++ linux-2.6.22-source/drivers/char/coserial.c
@@ -0,0 +1,395 @@
+/*
+ *  Copyright (C) 2004 Dan Aloni <da-x@colinux.org>
+ *
+ *  Cooperative Linux Serial Line implementation
+ *
+ *  Compatible with UML, also based on some code from there.
+ *  Also based on The tiny_tty.c example driver by Greg Kroah-Hartman (greg@kroah.com).
+ */
+
+/*
+ * 20040908: Ballard, Jonathan H. <jhballard@hotmail.com>
+ * 	   : Implemented cocd_task() & throttle.
+ * 20041224: Used schedule() instead of shedule_work().
+ * 20050101: Uses interruptible_sleep_on() and wake_up() instead of schedule().
+ *         : Uses list_*() for dispatched data flow to each unit.
+ *         : Handles multiple units in seperate tasks.
+ * 20050918: cocd_unit_task() waits for open complete. open_count was unsave.
+ * 20051023: Endless loop in cocd_unit_task has stopped other consoles.  From
+ *           schedule_work() should never endless loop. Use schedule_work()
+ *           instand of interruptible_sleep_on() and wake_up().
+ *           Remove serial console setup (currently not used).
+ *           Serial(-boot) console currently not enabled.
+ *
+*/
+
+
+#include <linux/major.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/stat.h>
+#include <linux/file.h>
+#include <linux/ioctl.h>
+#include <linux/device.h>
+#include <linux/console.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+
+#include <linux/workqueue.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+#include <linux/cooperative_internal.h>
+
+#include <asm/uaccess.h>
+
+static irqreturn_t cocd_interrupt(int irq, void *dev_id);
+
+struct cocd_tty {
+	struct semaphore	sem;		/* locks this structure */
+	struct tty_struct	*tty;		/* tty for this device */
+	unsigned		open_count;	/* open()/close() tally */
+	struct work_struct	work;		/* individual unit task */
+	struct list_head	inq;		/* input queue */
+	int			throttled;	/* data flow throttle bit */
+	int			throttle_index;	/* data flow throttle index */
+};
+
+static struct tty_driver *cocd_driver;
+DECLARE_MUTEX(cocd_sem);
+
+static void cocd_unit_task(struct work_struct *data)
+{
+	co_message_node_t *input;
+	struct cocd_tty *cocd = container_of(data, struct cocd_tty, work);
+	co_linux_message_t *message;
+	char *p, *e, *m;
+	struct tty_struct *tty;
+	int index;
+
+	if (!cocd)
+		return;
+
+	tty = cocd->tty;
+	if (!tty)
+		return;
+
+	down(&cocd->sem);
+	index = cocd->throttle_index;
+	while(cocd->open_count && !list_empty(&cocd->inq)) {
+		input = list_entry(cocd->inq.prev, co_message_node_t, node);
+		message = (co_linux_message_t *)&input->msg.data;
+
+		e = (m = p = message->data) + message->size;
+		p += index;
+		while(p < e && cocd->open_count) {
+			if(cocd->throttled) {
+				/* stop here, save position */
+				cocd->throttle_index = p - (char*)(message->data);
+				up(&cocd->sem);
+				return;
+			}
+			up(&cocd->sem);
+			m += tty_buffer_request_room(tty, e - m);
+			p += tty_insert_flip_string(tty, p, m - p);
+			tty_flip_buffer_push(tty);
+			down(&cocd->sem);
+		}
+		list_del(&input->node);
+		co_free_message(input);
+		cocd->throttle_index = index = 0;
+	}
+
+	if (cocd->open_count == 0) {
+		/* last close, clear all */
+		free_irq(SERIAL_IRQ, NULL);
+		tty->driver_data = NULL;
+		while(!list_empty(&cocd->inq)) {
+			input = list_entry(cocd->inq.prev, co_message_node_t, node);
+			list_del(&input->node);
+			co_free_message(input);
+		}
+		up(&cocd->sem); /* but nobody should wait here! */
+
+		kfree(cocd);
+		return;
+	}
+	up(&cocd->sem);
+
+	tty_flip_buffer_push(tty);
+}
+
+static int cocd_open(struct tty_struct *tty, struct file * filp)
+{
+	struct cocd_tty *cocd;
+
+	down(&cocd_sem);
+
+	if ((cocd = (struct cocd_tty *)tty->driver_data)) {
+		down(&cocd->sem);
+	} else {
+		int ret;
+
+		ret = request_irq(SERIAL_IRQ, &cocd_interrupt, IRQF_SAMPLE_RANDOM, "coserial", NULL);
+		if (ret) {
+			printk(KERN_ERR "COSERIAL: unable to get irq %d", SERIAL_IRQ);
+			return ret;
+		}
+
+		if(!(cocd = kmalloc(sizeof(*cocd), GFP_KERNEL))) {
+			up(&cocd_sem);
+			return -ENOMEM;
+		}
+
+		init_MUTEX_LOCKED(&cocd->sem);
+		cocd->open_count = 0;
+		cocd->tty = tty;
+		cocd->throttled = 0;
+		cocd->throttle_index = 0;
+		INIT_WORK(&cocd->work, cocd_unit_task);
+		INIT_LIST_HEAD(&cocd->inq);
+		tty->driver_data = cocd;
+		tty->low_latency = 1;
+	}
+
+	cocd->open_count++;
+
+	up(&cocd->sem);
+	up(&cocd_sem);
+
+	return 0;
+}
+
+static void cocd_close(struct tty_struct *tty, struct file * filp)
+{
+	struct cocd_tty *cocd;
+
+	down(&cocd_sem);
+
+	cocd = (struct cocd_tty *)tty->driver_data;
+	if (!cocd) {
+		printk(KERN_ERR "cocd: close no attached struct\n");
+		goto out;
+	}
+
+	down(&cocd->sem);
+	if (--cocd->open_count == 0)
+		schedule_work(&cocd->work);
+	up(&cocd->sem);
+
+out:
+
+	up(&cocd_sem);
+}
+
+static irqreturn_t cocd_interrupt(int irq, void *dev_id)
+{
+	co_message_node_t *input;
+	co_linux_message_t *message;
+	struct tty_struct *tty;
+	struct cocd_tty *cocd;
+
+	if (!cocd_driver)
+		return IRQ_NONE;
+
+	if(!co_get_message(&input, CO_DEVICE_SERIAL))
+		return IRQ_NONE;
+
+	if(!input)
+		return IRQ_NONE;
+
+	message = (co_linux_message_t *)&input->msg.data;
+	down(&cocd_sem);
+	if (message->unit < CO_MODULE_MAX_SERIAL
+	 && (tty = cocd_driver->ttys[message->unit])
+	 && (cocd = (struct cocd_tty *)tty->driver_data)) {
+		up(&cocd_sem);
+
+		down(&cocd->sem);
+		list_add_tail(&input->node,&cocd->inq);
+		if (!cocd->throttled)
+			schedule_work(&cocd->work);
+		up(&cocd->sem);
+
+		return IRQ_HANDLED;
+	}
+	up(&cocd_sem);
+	co_free_message(input); /* message lose */
+
+	return IRQ_NONE;
+}
+
+static int cocd_write(struct tty_struct * tty,
+	       const unsigned char *buf, int count)
+{
+	const char *kbuf_scan = buf;
+	int count_left = count;
+
+	while (count_left > 0) {
+		int count_partial = count_left;
+		if (count_partial > 1000)
+			count_partial = 1000;
+
+		co_send_message(CO_MODULE_LINUX,
+				CO_MODULE_SERIAL0 + tty->index,
+				CO_PRIORITY_DISCARDABLE,
+				CO_MESSAGE_TYPE_STRING,
+				count_partial,
+				kbuf_scan);
+
+		count_left -= count_partial;
+		kbuf_scan += count_partial;
+	}
+
+	return count;
+}
+
+static int cocd_write_room(struct tty_struct *tty)
+{
+	struct cocd_tty *cocd = (struct cocd_tty *)tty->driver_data;
+	if (!cocd)
+		return -ENODEV;
+
+	down(&cocd->sem);
+	if (cocd->open_count == 0) {
+		/* port was not opened */
+		up(&cocd->sem);
+		return -EINVAL;
+	}
+
+	up(&cocd->sem);
+	return 255;
+}
+
+static void cocd_hangup(struct tty_struct *tty)
+{
+}
+
+static void cocd_throttle(struct tty_struct * tty)
+{
+	struct cocd_tty *cocd = (struct cocd_tty *)tty->driver_data;
+	if (!cocd)
+		return;
+
+	down(&cocd->sem);
+	cocd->throttled = 1;
+	up(&cocd->sem);
+}
+
+static void cocd_unthrottle(struct tty_struct * tty)
+{
+	struct cocd_tty *cocd = (struct cocd_tty *)tty->driver_data;
+	if (!cocd)
+		return;
+
+	down(&cocd->sem);
+	cocd->throttled = 0;
+	schedule_work(&cocd->work);
+	up(&cocd->sem);
+}
+
+static void cocd_flush_buffer(struct tty_struct *tty)
+{
+}
+
+static void cocd_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
+{
+}
+
+static int cocd_chars_in_buffer(struct tty_struct *tty)
+{
+	return 0;
+}
+
+static struct tty_operations cocd_ops = {
+	.open = cocd_open,
+	.close = cocd_close,
+	.write = cocd_write,
+	.write_room = cocd_write_room,
+	.flush_buffer = cocd_flush_buffer,
+	.throttle = cocd_throttle,
+	.unthrottle = cocd_unthrottle,
+	.hangup = cocd_hangup,
+	.chars_in_buffer = cocd_chars_in_buffer,
+	.set_termios = cocd_set_termios,
+};
+
+static int __init cocd_init(void)
+{
+	int retval;
+
+    	cocd_driver = alloc_tty_driver(CO_MODULE_MAX_SERIAL);
+
+	if (!cocd_driver) {
+		printk(KERN_ERR "Couldn't allocate cocd driver");
+		return -ENOMEM;
+	}
+
+	cocd_driver->owner = THIS_MODULE;
+	cocd_driver->driver_name = "coserial";
+	cocd_driver->name = "ttyS";
+	cocd_driver->major = TTY_MAJOR;
+	cocd_driver->minor_start = 64;
+	cocd_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	cocd_driver->subtype = SERIAL_TYPE_NORMAL;
+	cocd_driver->init_termios = tty_std_termios;
+	cocd_driver->flags = 0;
+
+	tty_set_operations(cocd_driver, &cocd_ops);
+
+	retval = tty_register_driver(cocd_driver);
+	if (retval) {
+		printk(KERN_ERR "Couldn't register cocd driver");
+		put_tty_driver(cocd_driver);
+		return retval;
+	}
+
+	return 0;
+}
+
+static void __exit cocd_exit(void)
+{
+	tty_unregister_driver(cocd_driver);
+	put_tty_driver(cocd_driver);
+}
+
+module_init(cocd_init);
+module_exit(cocd_exit);
+
+/*
+ * ------------------------------------------------------------
+ * Serial console driver
+ * ------------------------------------------------------------
+ */
+#ifdef CONFIG_SERIAL_CONSOLE
+static void cocd_console_write(struct console *c, const char *string,  unsigned len)
+{
+}
+
+static struct tty_driver *cocd_console_device(struct console *c, int *index)
+{
+        *index = c->index;
+        return cocd_driver;
+}
+
+static struct console cocd_cons = {
+        name:           "ttyS",
+        write:          cocd_console_write,
+        device:         cocd_console_device,
+        flags:          CON_PRINTBUFFER,
+        index:          -1,
+};
+
+/*
+ *	Register console.
+ */
+static int __init cocd_console_init(void)
+{
+	register_console(&cocd_cons);
+	return 0;
+}
+console_initcall(cocd_console_init);
+#endif
