| /* |
| ** mux.c: |
| ** MUX console for the NOVA and K-Class systems. |
| ** |
| ** (c) Copyright 2002 Ryan Bradetich |
| ** (c) Copyright 2002 Hewlett-Packard Company |
| ** |
| ** 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; either version 2 of the License, or |
| ** (at your option) any later version. |
| ** |
| ** |
| ** This Driver used Christoph Plattner's pdc_console.c as a driver |
| ** template. |
| ** |
| ** This Driver currently only supports the console (port 0) on the MUX. |
| ** Additional work will be needed on this driver to enable the full |
| ** functionality of the MUX. |
| ** |
| */ |
| |
| static char *mux_drv_version = "0.1"; |
| |
| #include <linux/config.h> |
| #include <linux/version.h> |
| |
| #undef SERIAL_PARANOIA_CHECK |
| #define CONFIG_SERIAL_NOPAUSE_IO |
| #define SERIAL_DO_RESTART |
| |
| #include <linux/module.h> |
| #include <linux/serial.h> |
| #include <linux/serialP.h> |
| #include <linux/tty.h> |
| #include <linux/slab.h> |
| #include <linux/init.h> |
| #include <linux/delay.h> |
| #include <asm/uaccess.h> |
| |
| #ifdef CONFIG_MAGIC_SYSRQ |
| #include <linux/sysrq.h> |
| static unsigned long break_pressed; |
| #endif |
| |
| #ifdef CONFIG_GSC |
| #include <asm/gsc.h> |
| #endif |
| |
| static unsigned long hpa; |
| |
| #define MUX_OFFSET 0x800 |
| #define MUX_LINE_OFFSET 0x80 |
| |
| #define MUX_FIFO_SIZE 255 |
| #define MUX_MIN_FREE_SIZE 32 |
| |
| #define MUX_FIFO_DRAIN_DELAY 1 |
| #define MUX_POLL_DELAY (30 * HZ / 1000) |
| |
| #define IO_COMMAND_REG_OFFSET 0x30 |
| #define IO_STATUS_REG_OFFSET 0x34 |
| #define IO_DATA_REG_OFFSET 0x3c |
| #define IO_DCOUNT_REG_OFFSET 0x40 |
| #define IO_UCOUNT_REG_OFFSET 0x44 |
| #define IO_FIFOS_REG_OFFSET 0x48 |
| |
| #define MUX_EOFIFO(status) ((status & 0xF000) == 0xF000) |
| #define MUX_STATUS(status) ((status & 0xF000) == 0x8000) |
| #define MUX_BREAK(status) ((status & 0xF000) == 0x2000) |
| |
| static int mux_drv_refcount; /* = 0 */ |
| static struct tty_driver mux_drv_driver; |
| static struct async_struct *mux_drv_info; |
| static struct timer_list mux_drv_timer; |
| |
| #define NR_PORTS 1 |
| static struct tty_struct *mux_drv_table[NR_PORTS]; |
| static struct termios *mux_drv_termios[NR_PORTS]; |
| static struct termios *mux_drv_termios_locked[NR_PORTS]; |
| |
| /** |
| * mux_read_fifo - Read chars from the mux fifo. |
| * @info: Ptr to the async structure. |
| * |
| * This reads all available data from the mux's fifo and pushes |
| * the data to the tty layer. |
| */ |
| static void |
| mux_read_fifo(struct async_struct *info) |
| { |
| int data; |
| struct tty_struct *tty = info->tty; |
| |
| while(1) { |
| data = __raw_readl((unsigned long)info->iomem_base |
| + IO_DATA_REG_OFFSET); |
| |
| if (MUX_STATUS(data)) |
| continue; |
| |
| if (MUX_EOFIFO(data)) |
| break; |
| |
| if (tty->flip.count >= TTY_FLIPBUF_SIZE) |
| continue; |
| |
| *tty->flip.char_buf_ptr = data & 0xffu; |
| *tty->flip.flag_buf_ptr = 0; |
| |
| #ifdef CONFIG_MAGIC_SYSRQ |
| if (MUX_BREAK(data) && !break_pressed) { |
| break_pressed = jiffies; |
| continue; |
| } |
| |
| if(MUX_BREAK(data)) { |
| *tty->flip.flag_buf_ptr = TTY_BREAK; |
| } |
| |
| if(break_pressed) { |
| if(time_before(jiffies, break_pressed + HZ * 5)) { |
| handle_sysrq(data & 0xffu, NULL, NULL, NULL); |
| break_pressed = 0; |
| continue; |
| } |
| break_pressed = 0; |
| } |
| #endif |
| |
| tty->flip.flag_buf_ptr++; |
| tty->flip.char_buf_ptr++; |
| tty->flip.count++; |
| } |
| |
| tty_flip_buffer_push(tty); |
| } |
| |
| |
| /** |
| * mux_drv_poll - Mux poll function. |
| * @unused: Unused variable |
| * |
| * This function periodically polls the Mux to check for new data. |
| */ |
| static void |
| mux_drv_poll(unsigned long unused) |
| { |
| struct async_struct *info = mux_drv_info; |
| |
| if(info && info->tty && mux_drv_refcount) { |
| mux_read_fifo(info); |
| info->last_active = jiffies; |
| } |
| |
| mod_timer(&mux_drv_timer, jiffies + MUX_POLL_DELAY); |
| } |
| |
| /** |
| * mux_chars_in_buffer - Returns the number of chars present in the outbound fifo. |
| * @tty: Ptr to the tty structure. |
| * |
| * This function returns the number of chars sitting in the outbound fifo. |
| * [Note: This function is required for the normal_poll function in |
| * drivers/char/n_tty.c]. |
| */ |
| static int |
| mux_chars_in_buffer(struct tty_struct *tty) |
| { |
| struct async_struct *info = (struct async_struct *)tty->driver_data; |
| return __raw_readl((unsigned long)info->iomem_base |
| + IO_DCOUNT_REG_OFFSET); |
| } |
| |
| /** |
| * mux_flush_buffer - Pause until the fifo is empty. |
| * @tty: Ptr to the tty structure. |
| * |
| * Since the mux fifo is self draining, this function just |
| * waits until the fifo has completely drained. |
| */ |
| static void |
| mux_flush_buffer(struct tty_struct *tty) |
| { |
| while(mux_chars_in_buffer(tty)) |
| mdelay(MUX_FIFO_DRAIN_DELAY); |
| } |
| |
| /** |
| * mux_write_room - How much room is left in the fifo. |
| * @tty: Ptr to the tty structure. |
| * |
| * This function returns how much room is in the fifo for |
| * writing. |
| */ |
| static int |
| mux_write_room(struct tty_struct *tty) |
| { |
| int room = mux_chars_in_buffer(tty); |
| if(room > MUX_FIFO_SIZE) |
| return 0; |
| |
| return MUX_FIFO_SIZE - room; |
| } |
| |
| /** |
| * mux_write - Write chars to the mux fifo. |
| * @tty: Ptr to the tty structure. |
| * @from_user: Is the buffer from user space? |
| * @buf: The buffer to write to the mux fifo. |
| * @count: The number of chars to write to the mux fifo. |
| * |
| * This function writes the data from buf to the mux fifo. |
| * [Note: we need the mux_flush_buffer() at the end of the |
| * function, otherwise the system will wait for LONG_MAX |
| * if the fifo is not empty when the TCSETSW ioctl is called.] |
| */ |
| static int |
| mux_write(struct tty_struct *tty, int from_user, |
| const unsigned char *buf, int count) |
| { |
| int size, len, ret = count; |
| char buffer[MUX_FIFO_SIZE], *buf_p; |
| unsigned long iomem_base = |
| (unsigned long)((struct async_struct *)tty->driver_data)->iomem_base; |
| |
| while (count) { |
| size = mux_write_room(tty); |
| len = (size < count) ? size : count; |
| |
| if (from_user) { |
| copy_from_user(buffer, buf, len); |
| buf_p = buffer; |
| } else { |
| buf_p = (char *)buf; |
| } |
| |
| count -= len; |
| buf += len; |
| |
| if(size < MUX_MIN_FREE_SIZE) |
| mux_flush_buffer(tty); |
| |
| while(len--) { |
| __raw_writel(*buf_p++, iomem_base + IO_DATA_REG_OFFSET); |
| } |
| } |
| |
| mux_flush_buffer(tty); |
| return ret; |
| } |
| |
| /** |
| * mux_break - Turn break handling on or off. |
| * @tty: Ptr to the tty structure. |
| * @break_state: break value. |
| * |
| * This function must be defined because the send_break() in |
| * drivers/char/tty_io.c requires it. Currently the Serial Mux |
| * does nothing when this function is called. |
| */ |
| static void |
| mux_break(struct tty_struct *tty, int break_state) |
| { |
| } |
| |
| /** |
| * get_serial_info - Return the serial structure to userspace. |
| * @info: Ptr to the async structure. |
| * @retinfo: Ptr to the users space buffer. |
| * |
| * Fill in this serial structure and return it to userspace. |
| */ |
| static int |
| get_serial_info(struct async_struct *info, |
| struct serial_struct *retinfo) |
| { |
| struct serial_struct tmp; |
| |
| if (!retinfo) |
| return -EFAULT; |
| |
| memset(&tmp, 0, sizeof(tmp)); |
| tmp.line = info->line; |
| tmp.port = info->line; |
| tmp.flags = info->flags; |
| tmp.close_delay = info->close_delay; |
| return copy_to_user(retinfo, &tmp, sizeof(*retinfo)) ? -EFAULT : 0; |
| } |
| |
| /** |
| * get_modem_info - Return the modem control and status signals to userspace. |
| * @info: Ptr to the async structure. |
| * @value: The return buffer. |
| * |
| * The Serial MUX driver always returns these values to userspace: |
| * Data Terminal Ready, Carrier Detect, Clear To Send, |
| * Request To Send. |
| * |
| */ |
| static int |
| get_modem_info(struct async_struct *info, unsigned int *value) |
| { |
| unsigned int result = TIOCM_DTR|TIOCM_CAR|TIOCM_CTS|TIOCM_RTS; |
| return copy_to_user(value, &result, sizeof(int)) ? -EFAULT : 0; |
| } |
| |
| /** |
| * get_lsr_info - Return line status register info to userspace. |
| * @info: Ptr to the async structure. |
| * @value: The return buffer. |
| * |
| * The Serial MUX driver always returns empty transmitter to userspace. |
| */ |
| static int |
| get_lsr_info(struct async_struct *info, unsigned int *value) |
| { |
| unsigned int result = TIOCSER_TEMT; |
| return copy_to_user(value, &result, sizeof(int)) ? -EFAULT : 0; |
| } |
| |
| /** |
| * mux_ioctl - Handle driver specific ioctl commands. |
| * @tty: Ptr to the tty structure. |
| * @file: Unused. |
| * @cmd: The ioctl number. |
| * @arg: The ioctl argument. |
| * |
| * This function handles ioctls specific to the Serial MUX driver, |
| * or ioctls that need driver specific information. |
| * |
| */ |
| static int |
| mux_ioctl(struct tty_struct *tty, struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| struct async_struct *info = (struct async_struct *) tty->driver_data; |
| |
| if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && |
| (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) && |
| (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) { |
| if (tty->flags & (1 << TTY_IO_ERROR)) |
| return -EIO; |
| } |
| |
| switch (cmd) { |
| case TIOCMGET: |
| return get_modem_info(info, (unsigned int *) arg); |
| |
| case TIOCMBIS: |
| case TIOCMBIC: |
| case TIOCMSET: |
| return 0; |
| |
| case TIOCGSERIAL: |
| return get_serial_info(info, (struct serial_struct *) arg); |
| |
| case TIOCSSERIAL: |
| return 0; |
| |
| case TIOCSERCONFIG: |
| return 0; |
| |
| case TIOCSERGETLSR: |
| return get_lsr_info(info, (unsigned int *) arg); |
| |
| case TIOCSERGSTRUCT: |
| if (copy_to_user((struct async_struct *) arg, |
| info, sizeof (struct async_struct))) |
| return -EFAULT; |
| return 0; |
| |
| case TIOCMIWAIT: |
| return 0; |
| |
| case TIOCGICOUNT: |
| return 0; |
| |
| case TIOCSERGWILD: |
| case TIOCSERSWILD: |
| /* "setserial -W" is called in Debian boot */ |
| printk("TIOCSER?WILD ioctl obsolete, ignored.\n"); |
| return 0; |
| |
| default: |
| return -ENOIOCTLCMD; |
| } |
| return 0; |
| } |
| |
| /** |
| * mux_close - Close the serial mux driver. |
| * @tty: Ptr to the tty structure. |
| * @filp: Unused. |
| * |
| * This routine is called when the serial port gets closed. First, we |
| * wait for the last remaining data to be sent. Then, we unlink its |
| * async structure from the interrupt chain if necessary, and we free |
| * that IRQ if nothing is left in the chain. |
| */ |
| static void |
| mux_close(struct tty_struct *tty, struct file *filp) |
| { |
| struct async_struct *info = (struct async_struct *) tty->driver_data; |
| |
| mux_drv_refcount--; |
| if (mux_drv_refcount > 0) |
| return; |
| |
| info->flags |= ASYNC_CLOSING; |
| |
| /* |
| * Save the termios structure, since this port may have |
| * separate termios for callout and dialin. |
| */ |
| if (info->flags & ASYNC_NORMAL_ACTIVE) |
| info->state->normal_termios = *tty->termios; |
| if (info->flags & ASYNC_CALLOUT_ACTIVE) |
| info->state->callout_termios = *tty->termios; |
| |
| /* |
| * At this point we stop accepting input. To do this, we |
| * disable the receive line status interrupts, and tell the |
| * interrupt driver to stop checking the data ready bit in the |
| * line status register. |
| */ |
| |
| /* XXX CP: make mask for receive !!! */ |
| |
| if (tty->driver.flush_buffer) |
| tty->driver.flush_buffer(tty); |
| tty_ldisc_flush(tty); |
| tty->closing = 0; |
| info->event = 0; |
| info->tty = 0; |
| mux_drv_info = NULL; |
| if (info->blocked_open) { |
| if (info->close_delay) { |
| set_current_state(TASK_INTERRUPTIBLE); |
| schedule_timeout(info->close_delay); |
| } |
| wake_up_interruptible(&info->open_wait); |
| } |
| info->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CALLOUT_ACTIVE | |
| ASYNC_CLOSING); |
| wake_up_interruptible(&info->close_wait); |
| MOD_DEC_USE_COUNT; |
| } |
| |
| /** |
| * get_async_struct - Get the async structure. |
| * @line: Minor number of the tty device. |
| * @ret_info: Ptr to the newly allocated async structure. |
| * |
| * Allocate and return an async structure for the specified |
| * tty device line. |
| */ |
| static int |
| get_async_struct(int line, struct async_struct **ret_info) |
| { |
| struct async_struct *info; |
| |
| info = kmalloc(sizeof (struct async_struct), GFP_KERNEL); |
| if (!info) { |
| return -ENOMEM; |
| } |
| memset(info, 0, sizeof (struct async_struct)); |
| init_waitqueue_head(&info->open_wait); |
| init_waitqueue_head(&info->close_wait); |
| init_waitqueue_head(&info->delta_msr_wait); |
| info->magic = SERIAL_MAGIC; |
| info->port = 0; |
| info->flags = 0; |
| info->io_type = 0; |
| info->iomem_base = (void *)(hpa + MUX_OFFSET); |
| info->iomem_reg_shift = 0; |
| info->xmit_fifo_size = MUX_FIFO_SIZE; |
| info->line = line; |
| info->tqueue.routine = NULL; |
| info->tqueue.data = info; |
| info->state = NULL; |
| *ret_info = info; |
| return 0; |
| } |
| |
| /** |
| * mux_open - Open the serial mux driver. |
| * @tty: Ptr to the tty structure. |
| * @filp: Unused. |
| * |
| * This routine is called whenever a serial port is opened. It |
| * enables interrupts for a serial port, linking in its async structure |
| * into the IRQ chain. It also performs the serial-specific |
| * initialization for the tty structure. |
| */ |
| static int |
| mux_open(struct tty_struct *tty, struct file *filp) |
| { |
| struct async_struct *info; |
| int retval, line; |
| |
| MOD_INC_USE_COUNT; |
| line = MINOR(tty->device) - tty->driver.minor_start; |
| if ((line < 0) || (line >= NR_PORTS)) { |
| MOD_DEC_USE_COUNT; |
| return -ENODEV; |
| } |
| retval = get_async_struct(line, &info); |
| if (retval) { |
| MOD_DEC_USE_COUNT; |
| return retval; |
| } |
| |
| tty->driver_data = info; |
| info->tty = tty; |
| mux_drv_info = info; |
| info->tty->low_latency = 0; |
| info->session = current->session; |
| info->pgrp = current->pgrp; |
| mux_drv_refcount++; |
| return 0; |
| } |
| |
| /** |
| * mux_probe - Determine if the Serial Mux should claim this device. |
| * @dev: The parisc device. |
| * |
| * Deterimine if the Sserial Mux should claim this chip (return 0) |
| * or not (return 1). |
| */ |
| static int __init |
| mux_probe(struct parisc_device *dev) |
| { |
| if(hpa) { |
| printk(KERN_INFO "Serial MUX driver already registered, skipping additonal MUXes for now.\n"); |
| return 1; |
| } |
| |
| init_timer(&mux_drv_timer); |
| mux_drv_timer.function = mux_drv_poll; |
| mod_timer(&mux_drv_timer, jiffies + MUX_POLL_DELAY); |
| |
| hpa = dev->hpa; |
| printk(KERN_INFO "Serial MUX driver version %s at 0x%lx\n", |
| mux_drv_version, hpa); |
| |
| /* Initialize the tty_driver structure */ |
| |
| memset(&mux_drv_driver, 0, sizeof (struct tty_driver)); |
| mux_drv_driver.magic = TTY_DRIVER_MAGIC; |
| mux_drv_driver.driver_name = "Serial MUX driver"; |
| #ifdef CONFIG_DEVFS_FS |
| mux_drv_driver.name = "ttb/%d"; |
| #else |
| mux_drv_driver.name = "ttyB"; |
| #endif |
| mux_drv_driver.major = MUX_MAJOR; |
| mux_drv_driver.minor_start = 0; |
| mux_drv_driver.num = NR_PORTS; |
| mux_drv_driver.type = TTY_DRIVER_TYPE_SERIAL; |
| mux_drv_driver.subtype = SERIAL_TYPE_NORMAL; |
| mux_drv_driver.init_termios = tty_std_termios; |
| mux_drv_driver.init_termios.c_cflag = |
| B9600 | CS8 | CREAD | HUPCL | CLOCAL; |
| mux_drv_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; |
| mux_drv_driver.refcount = &mux_drv_refcount; |
| mux_drv_driver.table = mux_drv_table; |
| mux_drv_driver.termios = mux_drv_termios; |
| mux_drv_driver.termios_locked = mux_drv_termios_locked; |
| |
| mux_drv_driver.open = mux_open; |
| mux_drv_driver.close = mux_close; |
| mux_drv_driver.write = mux_write; |
| mux_drv_driver.put_char = NULL; |
| mux_drv_driver.flush_chars = NULL; |
| mux_drv_driver.write_room = mux_write_room; |
| mux_drv_driver.chars_in_buffer = mux_chars_in_buffer; |
| mux_drv_driver.flush_buffer = mux_flush_buffer; |
| mux_drv_driver.ioctl = mux_ioctl; |
| mux_drv_driver.throttle = NULL; |
| mux_drv_driver.unthrottle = NULL; |
| mux_drv_driver.set_termios = NULL; |
| mux_drv_driver.stop = NULL; |
| mux_drv_driver.start = NULL; |
| mux_drv_driver.hangup = NULL; |
| mux_drv_driver.break_ctl = mux_break; |
| mux_drv_driver.send_xchar = NULL; |
| mux_drv_driver.wait_until_sent = NULL; |
| mux_drv_driver.read_proc = NULL; |
| |
| if (tty_register_driver(&mux_drv_driver)) |
| panic("Could not register the serial MUX driver\n"); |
| |
| return 0; |
| } |
| |
| static struct parisc_device_id mux_tbl[] = { |
| { HPHW_A_DIRECT, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0000D }, |
| { 0, } |
| }; |
| |
| MODULE_DEVICE_TABLE(parisc, mux_tbl); |
| |
| static struct parisc_driver mux_driver = { |
| name: "Serial MUX driver", |
| id_table: mux_tbl, |
| probe: mux_probe, |
| }; |
| |
| /** |
| * mux_init - Serial MUX initalization procedure. |
| * |
| * Register the Serial MUX driver. |
| */ |
| static int __init mux_init(void) |
| { |
| return register_parisc_driver(&mux_driver); |
| } |
| |
| /** |
| * mux_exit - Serial MUX cleanup procedure. |
| * |
| * Unregister the Serial MUX driver from the tty layer. |
| */ |
| static void __exit mux_exit(void) |
| { |
| int status = tty_unregister_driver(&mux_drv_driver); |
| if(status) { |
| printk("MUX: failed to unregister the Serial MUX driver (%d)\n", status); |
| } |
| } |
| |
| module_init(mux_init); |
| module_exit(mux_exit); |
| MODULE_DESCRIPTION("Serial MUX driver"); |
| MODULE_AUTHOR("Ryan Bradetich"); |
| MODULE_LICENSE("GPL"); |