diff options
Diffstat (limited to 'tty')
-rw-r--r-- | tty/Makefile | 41 | ||||
-rw-r--r-- | tty/tiny_serial.c | 291 | ||||
-rw-r--r-- | tty/tiny_tty.c | 591 |
3 files changed, 923 insertions, 0 deletions
diff --git a/tty/Makefile b/tty/Makefile new file mode 100644 index 0000000..f5b8592 --- /dev/null +++ b/tty/Makefile @@ -0,0 +1,41 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + + +# Add your debugging flag (or not) to CFLAGS +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) +CFLAGS += -I.. + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := tiny_tty.o tiny_serial.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +endif + + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/tty/tiny_serial.c b/tty/tiny_serial.c new file mode 100644 index 0000000..1ae7a43 --- /dev/null +++ b/tty/tiny_serial.c @@ -0,0 +1,291 @@ +/* + * Tiny Serial driver + * + * Copyright (C) 2002-2004 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver shows how to create a minimal serial driver. It does not rely on + * any backing hardware, but creates a timer that emulates data being received + * from some kind of hardware. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/module.h> + + +#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>" +#define DRIVER_DESC "Tiny serial driver" + +/* Module information */ +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + +#define DELAY_TIME HZ * 2 /* 2 seconds per character */ +#define TINY_DATA_CHARACTER 't' + +#define TINY_SERIAL_MAJOR 240 /* experimental range */ +#define TINY_SERIAL_MINORS 1 /* only have one minor */ +#define UART_NR 1 /* only use one port */ + +#define TINY_SERIAL_NAME "ttytiny" + +#define MY_NAME TINY_SERIAL_NAME + +static struct timer_list *timer; + +static void tiny_stop_tx(struct uart_port *port, unsigned int tty_stop) +{ +} + +static void tiny_stop_rx(struct uart_port *port) +{ +} + +static void tiny_enable_ms(struct uart_port *port) +{ +} + +static void tiny_tx_chars(struct uart_port *port) +{ + struct circ_buf *xmit = &port->info->xmit; + int count; + + if (port->x_char) { + pr_debug("wrote %2x", port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + tiny_stop_tx(port, 0); + return; + } + + count = port->fifosize >> 1; + do { + pr_debug("wrote %2x", xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + if (uart_circ_empty(xmit)) + break; + } while (--count > 0); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + tiny_stop_tx(port, 0); +} + +static void tiny_start_tx(struct uart_port *port, unsigned int tty_start) +{ +} + +static void tiny_timer(unsigned long data) +{ + struct uart_port *port; + struct tty_struct *tty; + + + port = (struct uart_port *)data; + if (!port) + return; + if (!port->info) + return; + tty = port->info->tty; + if (!tty) + return; + + /* add one character to the tty port */ + /* this doesn't actually push the data through unless tty->low_latency is set */ + tty_insert_flip_char(tty, TINY_DATA_CHARACTER, 0); + + tty_flip_buffer_push(tty); + + /* resubmit the timer again */ + timer->expires = jiffies + DELAY_TIME; + add_timer(timer); + + /* see if we have any data to transmit */ + tiny_tx_chars(port); +} + +static unsigned int tiny_tx_empty(struct uart_port *port) +{ + return 0; +} + +static unsigned int tiny_get_mctrl(struct uart_port *port) +{ + return 0; +} + +static void tiny_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static void tiny_break_ctl(struct uart_port *port, int break_state) +{ +} + +static void tiny_set_termios(struct uart_port *port, + struct termios *new, struct termios *old) +{ + int baud, quot, cflag = new->c_cflag; + /* get the byte size */ + switch (cflag & CSIZE) { + case CS5: + printk(KERN_DEBUG " - data bits = 5\n"); + break; + case CS6: + printk(KERN_DEBUG " - data bits = 6\n"); + break; + case CS7: + printk(KERN_DEBUG " - data bits = 7\n"); + break; + default: // CS8 + printk(KERN_DEBUG " - data bits = 8\n"); + break; + } + + /* determine the parity */ + if (cflag & PARENB) + if (cflag & PARODD) + pr_debug(" - parity = odd\n"); + else + pr_debug(" - parity = even\n"); + else + pr_debug(" - parity = none\n"); + + /* figure out the stop bits requested */ + if (cflag & CSTOPB) + pr_debug(" - stop bits = 2\n"); + else + pr_debug(" - stop bits = 1\n"); + + /* figure out the flow control settings */ + if (cflag & CRTSCTS) + pr_debug(" - RTS/CTS is enabled\n"); + else + pr_debug(" - RTS/CTS is disabled\n"); + + /* Set baud rate */ + baud = uart_get_baud_rate(port, new, old, 0, port->uartclk/16); + quot = uart_get_divisor(port, baud); + + //UART_PUT_DIV_LO(port, (quot & 0xff)); + //UART_PUT_DIV_HI(port, ((quot & 0xf00) >> 8)); +} + +static int tiny_startup(struct uart_port *port) +{ + /* this is the first time this port is opened */ + /* do any hardware initialization needed here */ + + /* create our timer and submit it */ + if (!timer) { + timer = kmalloc(sizeof(*timer), GFP_KERNEL); + if (!timer) + return -ENOMEM; + } + timer->data = (unsigned long)port; + timer->expires = jiffies + DELAY_TIME; + timer->function = tiny_timer; + add_timer(timer); + return 0; +} + +static void tiny_shutdown(struct uart_port *port) +{ + /* The port is being closed by the last user. */ + /* Do any hardware specific stuff here */ + + /* shut down our timer */ + del_timer(timer); +} + +static const char *tiny_type(struct uart_port *port) +{ + return "tinytty"; +} + +static void tiny_release_port(struct uart_port *port) +{ + +} + +static int tiny_request_port(struct uart_port *port) +{ + return 0; +} + +static void tiny_config_port(struct uart_port *port, int flags) +{ +} + +static int tiny_verify_port(struct uart_port *port, struct serial_struct *ser) +{ + return 0; +} + +static struct uart_ops tiny_ops = { + .tx_empty = tiny_tx_empty, + .set_mctrl = tiny_set_mctrl, + .get_mctrl = tiny_get_mctrl, + .stop_tx = tiny_stop_tx, + .start_tx = tiny_start_tx, + .stop_rx = tiny_stop_rx, + .enable_ms = tiny_enable_ms, + .break_ctl = tiny_break_ctl, + .startup = tiny_startup, + .shutdown = tiny_shutdown, + .set_termios = tiny_set_termios, + .type = tiny_type, + .release_port = tiny_release_port, + .request_port = tiny_request_port, + .config_port = tiny_config_port, + .verify_port = tiny_verify_port, +}; + +static struct uart_port tiny_port = { + .ops = &tiny_ops, +}; + +static struct uart_driver tiny_reg = { + .owner = THIS_MODULE, + .driver_name = TINY_SERIAL_NAME, + .dev_name = TINY_SERIAL_NAME, + .major = TINY_SERIAL_MAJOR, + .minor = TINY_SERIAL_MINORS, + .nr = UART_NR, +}; + +static int __init tiny_init(void) +{ + int result; + + printk(KERN_INFO "Tiny serial driver loaded\n"); + + result = uart_register_driver(&tiny_reg); + if (result) + return result; + + result = uart_add_one_port(&tiny_reg, &tiny_port); + if (result) + uart_unregister_driver(&tiny_reg); + + return result; +} + +module_init(tiny_init); diff --git a/tty/tiny_tty.c b/tty/tiny_tty.c new file mode 100644 index 0000000..778b207 --- /dev/null +++ b/tty/tiny_tty.c @@ -0,0 +1,591 @@ +/* + * Tiny TTY driver + * + * Copyright (C) 2002-2004 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver shows how to create a minimal tty driver. It does not rely on + * any backing hardware, but creates a timer that emulates data being received + * from some kind of hardware. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <asm/uaccess.h> + + +#define DRIVER_VERSION "v2.0" +#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>" +#define DRIVER_DESC "Tiny TTY driver" + +/* Module information */ +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + +#define DELAY_TIME HZ * 2 /* 2 seconds per character */ +#define TINY_DATA_CHARACTER 't' + +#define TINY_TTY_MAJOR 240 /* experimental range */ +#define TINY_TTY_MINORS 4 /* only have 4 devices */ + +struct tiny_serial { + struct tty_struct *tty; /* pointer to the tty for this device */ + int open_count; /* number of times this port has been opened */ + struct semaphore sem; /* locks this structure */ + struct timer_list *timer; + + /* for tiocmget and tiocmset functions */ + int msr; /* MSR shadow */ + int mcr; /* MCR shadow */ + + /* for ioctl fun */ + struct serial_struct serial; + wait_queue_head_t wait; + struct async_icount icount; +}; + +static struct tiny_serial *tiny_table[TINY_TTY_MINORS]; /* initially all NULL */ + + +static void tiny_timer(unsigned long timer_data) +{ + struct tiny_serial *tiny = (struct tiny_serial *)timer_data; + struct tty_struct *tty; + int i; + char data[1] = {TINY_DATA_CHARACTER}; + int data_size = 1; + + if (!tiny) + return; + + tty = tiny->tty; + + /* send the data to the tty layer for users to read. This doesn't + * actually push the data through unless tty->low_latency is set */ + for (i = 0; i < data_size; ++i) { + if (tty->flip.count >= TTY_FLIPBUF_SIZE) + tty_flip_buffer_push(tty); + tty_insert_flip_char(tty, data[i], TTY_NORMAL); + } + tty_flip_buffer_push(tty); + + /* resubmit the timer again */ + tiny->timer->expires = jiffies + DELAY_TIME; + add_timer(tiny->timer); +} + +static int tiny_open(struct tty_struct *tty, struct file *file) +{ + struct tiny_serial *tiny; + struct timer_list *timer; + int index; + + /* initialize the pointer in case something fails */ + tty->driver_data = NULL; + + /* get the serial object associated with this tty pointer */ + index = tty->index; + tiny = tiny_table[index]; + if (tiny == NULL) { + /* first time accessing this device, let's create it */ + tiny = kmalloc(sizeof(*tiny), GFP_KERNEL); + if (!tiny) + return -ENOMEM; + + init_MUTEX(&tiny->sem); + tiny->open_count = 0; + tiny->timer = NULL; + + tiny_table[index] = tiny; + } + + down(&tiny->sem); + + /* save our structure within the tty structure */ + tty->driver_data = tiny; + tiny->tty = tty; + + ++tiny->open_count; + if (tiny->open_count == 1) { + /* this is the first time this port is opened */ + /* do any hardware initialization needed here */ + + /* create our timer and submit it */ + if (!tiny->timer) { + timer = kmalloc(sizeof(*timer), GFP_KERNEL); + if (!timer) { + up(&tiny->sem); + return -ENOMEM; + } + tiny->timer = timer; + } + tiny->timer->data = (unsigned long )tiny; + tiny->timer->expires = jiffies + DELAY_TIME; + tiny->timer->function = tiny_timer; + add_timer(tiny->timer); + } + + up(&tiny->sem); + return 0; +} + +static void do_close(struct tiny_serial *tiny) +{ + down(&tiny->sem); + + if (!tiny->open_count) { + /* port was never opened */ + goto exit; + } + + --tiny->open_count; + if (tiny->open_count <= 0) { + /* The port is being closed by the last user. */ + /* Do any hardware specific stuff here */ + + /* shut down our timer */ + del_timer(tiny->timer); + } +exit: + up(&tiny->sem); +} + +static void tiny_close(struct tty_struct *tty, struct file *file) +{ + struct tiny_serial *tiny = tty->driver_data; + + if (tiny) + do_close(tiny); +} + +static int tiny_write(struct tty_struct *tty, + const unsigned char *buffer, int count) +{ + struct tiny_serial *tiny = tty->driver_data; + int i; + int retval = -EINVAL; + + if (!tiny) + return -ENODEV; + + down(&tiny->sem); + + if (!tiny->open_count) + /* port was not opened */ + goto exit; + + /* fake sending the data out a hardware port by + * writing it to the kernel debug log. + */ + printk(KERN_DEBUG "%s - ", __FUNCTION__); + for (i = 0; i < count; ++i) + printk("%02x ", buffer[i]); + printk("\n"); + +exit: + up(&tiny->sem); + return retval; +} + +static int tiny_write_room(struct tty_struct *tty) +{ + struct tiny_serial *tiny = tty->driver_data; + int room = -EINVAL; + + if (!tiny) + return -ENODEV; + + down(&tiny->sem); + + if (!tiny->open_count) { + /* port was not opened */ + goto exit; + } + + /* calculate how much room is left in the device */ + room = 255; + +exit: + up(&tiny->sem); + return room; +} + +#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + +static void tiny_set_termios(struct tty_struct *tty, struct termios *old_termios) +{ + unsigned int cflag; + + cflag = tty->termios->c_cflag; + + /* check that they really want us to change something */ + if (old_termios) { + if ((cflag == old_termios->c_cflag) && + (RELEVANT_IFLAG(tty->termios->c_iflag) == + RELEVANT_IFLAG(old_termios->c_iflag))) { + printk(KERN_DEBUG " - nothing to change...\n"); + return; + } + } + + /* get the byte size */ + switch (cflag & CSIZE) { + case CS5: + printk(KERN_DEBUG " - data bits = 5\n"); + break; + case CS6: + printk(KERN_DEBUG " - data bits = 6\n"); + break; + case CS7: + printk(KERN_DEBUG " - data bits = 7\n"); + break; + default: + case CS8: + printk(KERN_DEBUG " - data bits = 8\n"); + break; + } + + /* determine the parity */ + if (cflag & PARENB) + if (cflag & PARODD) + printk(KERN_DEBUG " - parity = odd\n"); + else + printk(KERN_DEBUG " - parity = even\n"); + else + printk(KERN_DEBUG " - parity = none\n"); + + /* figure out the stop bits requested */ + if (cflag & CSTOPB) + printk(KERN_DEBUG " - stop bits = 2\n"); + else + printk(KERN_DEBUG " - stop bits = 1\n"); + + /* figure out the hardware flow control settings */ + if (cflag & CRTSCTS) + printk(KERN_DEBUG " - RTS/CTS is enabled\n"); + else + printk(KERN_DEBUG " - RTS/CTS is disabled\n"); + + /* determine software flow control */ + /* if we are implementing XON/XOFF, set the start and + * stop character in the device */ + if (I_IXOFF(tty) || I_IXON(tty)) { + unsigned char stop_char = STOP_CHAR(tty); + unsigned char start_char = START_CHAR(tty); + + /* if we are implementing INBOUND XON/XOFF */ + if (I_IXOFF(tty)) + printk(KERN_DEBUG " - INBOUND XON/XOFF is enabled, " + "XON = %2x, XOFF = %2x", start_char, stop_char); + else + printk(KERN_DEBUG" - INBOUND XON/XOFF is disabled"); + + /* if we are implementing OUTBOUND XON/XOFF */ + if (I_IXON(tty)) + printk(KERN_DEBUG" - OUTBOUND XON/XOFF is enabled, " + "XON = %2x, XOFF = %2x", start_char, stop_char); + else + printk(KERN_DEBUG" - OUTBOUND XON/XOFF is disabled"); + } + + /* get the baud rate wanted */ + printk(KERN_DEBUG " - baud rate = %d", tty_get_baud_rate(tty)); +} + +/* Our fake UART values */ +#define MCR_DTR 0x01 +#define MCR_RTS 0x02 +#define MCR_LOOP 0x04 +#define MSR_CTS 0x08 +#define MSR_CD 0x10 +#define MSR_RI 0x20 +#define MSR_DSR 0x40 + +static int tiny_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct tiny_serial *tiny = tty->driver_data; + + unsigned int result = 0; + unsigned int msr = tiny->msr; + unsigned int mcr = tiny->mcr; + + result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0) | /* DTR is set */ + ((mcr & MCR_RTS) ? TIOCM_RTS : 0) | /* RTS is set */ + ((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) | /* LOOP is set */ + ((msr & MSR_CTS) ? TIOCM_CTS : 0) | /* CTS is set */ + ((msr & MSR_CD) ? TIOCM_CAR : 0) | /* Carrier detect is set*/ + ((msr & MSR_RI) ? TIOCM_RI : 0) | /* Ring Indicator is set */ + ((msr & MSR_DSR) ? TIOCM_DSR : 0); /* DSR is set */ + + return result; +} + +static int tiny_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct tiny_serial *tiny = tty->driver_data; + unsigned int mcr = tiny->mcr; + + if (set & TIOCM_RTS) + mcr |= MCR_RTS; + if (set & TIOCM_DTR) + mcr |= MCR_RTS; + + if (clear & TIOCM_RTS) + mcr &= ~MCR_RTS; + if (clear & TIOCM_DTR) + mcr &= ~MCR_RTS; + + /* set the new MCR value in the device */ + tiny->mcr = mcr; + return 0; +} + +static int tiny_read_proc(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + struct tiny_serial *tiny; + off_t begin = 0; + int length = 0; + int i; + + length += sprintf(page, "tinyserinfo:1.0 driver:%s\n", DRIVER_VERSION); + for (i = 0; i < TINY_TTY_MINORS && length < PAGE_SIZE; ++i) { + tiny = tiny_table[i]; + if (tiny == NULL) + continue; + + length += sprintf(page+length, "%d\n", i); + if ((length + begin) > (off + count)) + goto done; + if ((length + begin) < off) { + begin += length; + length = 0; + } + } + *eof = 1; +done: + if (off >= (length + begin)) + return 0; + *start = page + (off-begin); + return (count < begin+length-off) ? count : begin + length-off; +} + +#define tiny_ioctl tiny_ioctl_tiocgserial +static int tiny_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct tiny_serial *tiny = tty->driver_data; + + if (cmd == TIOCGSERIAL) { + struct serial_struct tmp; + + if (!arg) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + + tmp.type = tiny->serial.type; + tmp.line = tiny->serial.line; + tmp.port = tiny->serial.port; + tmp.irq = tiny->serial.irq; + tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + tmp.xmit_fifo_size = tiny->serial.xmit_fifo_size; + tmp.baud_base = tiny->serial.baud_base; + tmp.close_delay = 5*HZ; + tmp.closing_wait = 30*HZ; + tmp.custom_divisor = tiny->serial.custom_divisor; + tmp.hub6 = tiny->serial.hub6; + tmp.io_type = tiny->serial.io_type; + + if (copy_to_user((void __user *)arg, &tmp, sizeof(struct serial_struct))) + return -EFAULT; + return 0; + } + return -ENOIOCTLCMD; +} +#undef tiny_ioctl + +#define tiny_ioctl tiny_ioctl_tiocmiwait +static int tiny_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct tiny_serial *tiny = tty->driver_data; + + if (cmd == TIOCMIWAIT) { + DECLARE_WAITQUEUE(wait, current); + struct async_icount cnow; + struct async_icount cprev; + + cprev = tiny->icount; + while (1) { + add_wait_queue(&tiny->wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + remove_wait_queue(&tiny->wait, &wait); + + /* see if a signal woke us up */ + if (signal_pending(current)) + return -ERESTARTSYS; + + cnow = tiny->icount; + if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && + cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) + return -EIO; /* no change => error */ + if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || + ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || + ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || + ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) { + return 0; + } + cprev = cnow; + } + + } + return -ENOIOCTLCMD; +} +#undef tiny_ioctl + +#define tiny_ioctl tiny_ioctl_tiocgicount +static int tiny_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct tiny_serial *tiny = tty->driver_data; + + if (cmd == TIOCGICOUNT) { + struct async_icount cnow = tiny->icount; + struct serial_icounter_struct icount; + + icount.cts = cnow.cts; + icount.dsr = cnow.dsr; + icount.rng = cnow.rng; + icount.dcd = cnow.dcd; + icount.rx = cnow.rx; + icount.tx = cnow.tx; + icount.frame = cnow.frame; + icount.overrun = cnow.overrun; + icount.parity = cnow.parity; + icount.brk = cnow.brk; + icount.buf_overrun = cnow.buf_overrun; + + if (copy_to_user((void __user *)arg, &icount, sizeof(icount))) + return -EFAULT; + return 0; + } + return -ENOIOCTLCMD; +} +#undef tiny_ioctl + +/* the real tiny_ioctl function. The above is done to get the small functions in the book */ +static int tiny_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case TIOCGSERIAL: + return tiny_ioctl_tiocgserial(tty, file, cmd, arg); + case TIOCMIWAIT: + return tiny_ioctl_tiocmiwait(tty, file, cmd, arg); + case TIOCGICOUNT: + return tiny_ioctl_tiocgicount(tty, file, cmd, arg); + } + + return -ENOIOCTLCMD; +} + +static struct tty_operations serial_ops = { + .open = tiny_open, + .close = tiny_close, + .write = tiny_write, + .write_room = tiny_write_room, + .set_termios = tiny_set_termios, +}; + +static struct tty_driver *tiny_tty_driver; + +static int __init tiny_init(void) +{ + int retval; + int i; + + /* allocate the tty driver */ + tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS); + if (!tiny_tty_driver) + return -ENOMEM; + + /* initialize the tty driver */ + tiny_tty_driver->owner = THIS_MODULE; + tiny_tty_driver->driver_name = "tiny_tty"; + tiny_tty_driver->name = "ttty"; + tiny_tty_driver->devfs_name = "tts/ttty%d"; + tiny_tty_driver->major = TINY_TTY_MAJOR, + tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL, + tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS, + tiny_tty_driver->init_termios = tty_std_termios; + tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty_set_operations(tiny_tty_driver, &serial_ops); + + /* hack to make the book purty, yet still use these functions in the + * real driver. They really should be set up in the serial_ops + * structure above... */ + tiny_tty_driver->read_proc = tiny_read_proc; + tiny_tty_driver->tiocmget = tiny_tiocmget; + tiny_tty_driver->tiocmset = tiny_tiocmset; + tiny_tty_driver->ioctl = tiny_ioctl; + + /* register the tty driver */ + retval = tty_register_driver(tiny_tty_driver); + if (retval) { + printk(KERN_ERR "failed to register tiny tty driver"); + put_tty_driver(tiny_tty_driver); + return retval; + } + + for (i = 0; i < TINY_TTY_MINORS; ++i) + tty_register_device(tiny_tty_driver, i, NULL); + + printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION); + return retval; +} + +static void __exit tiny_exit(void) +{ + struct tiny_serial *tiny; + int i; + + for (i = 0; i < TINY_TTY_MINORS; ++i) + tty_unregister_device(tiny_tty_driver, i); + tty_unregister_driver(tiny_tty_driver); + + /* shut down all of the timers and free the memory */ + for (i = 0; i < TINY_TTY_MINORS; ++i) { + tiny = tiny_table[i]; + if (tiny) { + /* close the port */ + while (tiny->open_count) + do_close(tiny); + + /* shut down our timer and free the memory */ + del_timer(tiny->timer); + kfree(tiny->timer); + kfree(tiny); + tiny_table[i] = NULL; + } + } +} + +module_init(tiny_init); +module_exit(tiny_exit); |