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 + * + * 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. + * : 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +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