From 57f0f512b273f60d52568b8c6b77e17f5636edc0 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Wed, 5 Aug 2015 17:04:01 -0300 Subject: Initial import --- drivers/staging/dgnc/dgnc_tty.c | 3057 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 3057 insertions(+) create mode 100644 drivers/staging/dgnc/dgnc_tty.c (limited to 'drivers/staging/dgnc/dgnc_tty.c') diff --git a/drivers/staging/dgnc/dgnc_tty.c b/drivers/staging/dgnc/dgnc_tty.c new file mode 100644 index 000000000..ce4187f60 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_tty.c @@ -0,0 +1,3057 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +/************************************************************************ + * + * This file implements the tty driver functionality for the + * Neo and ClassicBoard PCI based product lines. + * + ************************************************************************ + * + */ + +#include +#include /* For jiffies, task states */ +#include /* For tasklet and interrupt structs/defines */ +#include +#include +#include +#include +#include +#include +#include +#include /* For udelay */ +#include /* For copy_from_user/copy_to_user */ +#include +#include "dgnc_driver.h" +#include "dgnc_tty.h" +#include "dgnc_neo.h" +#include "dgnc_cls.h" +#include "dgnc_sysfs.h" +#include "dgnc_utils.h" + +#define init_MUTEX(sem) sema_init(sem, 1) +#define DECLARE_MUTEX(name) \ + struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1) + +/* + * internal variables + */ +static struct dgnc_board *dgnc_BoardsByMajor[256]; +static unsigned char *dgnc_TmpWriteBuf; +static DECLARE_MUTEX(dgnc_TmpWriteSem); + +/* + * Default transparent print information. + */ +static struct digi_t dgnc_digi_init = { + .digi_flags = DIGI_COOK, /* Flags */ + .digi_maxcps = 100, /* Max CPS */ + .digi_maxchar = 50, /* Max chars in print queue */ + .digi_bufsize = 100, /* Printer buffer size */ + .digi_onlen = 4, /* size of printer on string */ + .digi_offlen = 4, /* size of printer off string */ + .digi_onstr = "\033[5i", /* ANSI printer on string ] */ + .digi_offstr = "\033[4i", /* ANSI printer off string ] */ + .digi_term = "ansi" /* default terminal type */ +}; + + +/* + * Define a local default termios struct. All ports will be created + * with this termios initially. + * + * This defines a raw port at 9600 baud, 8 data bits, no parity, + * 1 stop bit. + */ +static struct ktermios DgncDefaultTermios = { + .c_iflag = (DEFAULT_IFLAGS), /* iflags */ + .c_oflag = (DEFAULT_OFLAGS), /* oflags */ + .c_cflag = (DEFAULT_CFLAGS), /* cflags */ + .c_lflag = (DEFAULT_LFLAGS), /* lflags */ + .c_cc = INIT_C_CC, + .c_line = 0, +}; + + +/* Our function prototypes */ +static int dgnc_tty_open(struct tty_struct *tty, struct file *file); +static void dgnc_tty_close(struct tty_struct *tty, struct file *file); +static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch); +static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg); +static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo); +static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info); +static int dgnc_tty_write_room(struct tty_struct *tty); +static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c); +static int dgnc_tty_chars_in_buffer(struct tty_struct *tty); +static void dgnc_tty_start(struct tty_struct *tty); +static void dgnc_tty_stop(struct tty_struct *tty); +static void dgnc_tty_throttle(struct tty_struct *tty); +static void dgnc_tty_unthrottle(struct tty_struct *tty); +static void dgnc_tty_flush_chars(struct tty_struct *tty); +static void dgnc_tty_flush_buffer(struct tty_struct *tty); +static void dgnc_tty_hangup(struct tty_struct *tty); +static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value); +static int dgnc_get_modem_info(struct channel_t *ch, unsigned int __user *value); +static int dgnc_tty_tiocmget(struct tty_struct *tty); +static int dgnc_tty_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); +static int dgnc_tty_send_break(struct tty_struct *tty, int msec); +static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout); +static int dgnc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count); +static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios); +static void dgnc_tty_send_xchar(struct tty_struct *tty, char ch); + + +static const struct tty_operations dgnc_tty_ops = { + .open = dgnc_tty_open, + .close = dgnc_tty_close, + .write = dgnc_tty_write, + .write_room = dgnc_tty_write_room, + .flush_buffer = dgnc_tty_flush_buffer, + .chars_in_buffer = dgnc_tty_chars_in_buffer, + .flush_chars = dgnc_tty_flush_chars, + .ioctl = dgnc_tty_ioctl, + .set_termios = dgnc_tty_set_termios, + .stop = dgnc_tty_stop, + .start = dgnc_tty_start, + .throttle = dgnc_tty_throttle, + .unthrottle = dgnc_tty_unthrottle, + .hangup = dgnc_tty_hangup, + .put_char = dgnc_tty_put_char, + .tiocmget = dgnc_tty_tiocmget, + .tiocmset = dgnc_tty_tiocmset, + .break_ctl = dgnc_tty_send_break, + .wait_until_sent = dgnc_tty_wait_until_sent, + .send_xchar = dgnc_tty_send_xchar +}; + +/************************************************************************ + * + * TTY Initialization/Cleanup Functions + * + ************************************************************************/ + +/* + * dgnc_tty_preinit() + * + * Initialize any global tty related data before we download any boards. + */ +int dgnc_tty_preinit(void) +{ + /* + * Allocate a buffer for doing the copy from user space to + * kernel space in dgnc_write(). We only use one buffer and + * control access to it with a semaphore. If we are paging, we + * are already in trouble so one buffer won't hurt much anyway. + * + * We are okay to sleep in the malloc, as this routine + * is only called during module load, (not in interrupt context), + * and with no locks held. + */ + dgnc_TmpWriteBuf = kmalloc(WRITEBUFLEN, GFP_KERNEL); + + if (!dgnc_TmpWriteBuf) + return -ENOMEM; + + return 0; +} + + +/* + * dgnc_tty_register() + * + * Init the tty subsystem for this board. + */ +int dgnc_tty_register(struct dgnc_board *brd) +{ + int rc = 0; + + brd->SerialDriver.magic = TTY_DRIVER_MAGIC; + + snprintf(brd->SerialName, MAXTTYNAMELEN, "tty_dgnc_%d_", brd->boardnum); + + brd->SerialDriver.name = brd->SerialName; + brd->SerialDriver.name_base = 0; + brd->SerialDriver.major = 0; + brd->SerialDriver.minor_start = 0; + brd->SerialDriver.num = brd->maxports; + brd->SerialDriver.type = TTY_DRIVER_TYPE_SERIAL; + brd->SerialDriver.subtype = SERIAL_TYPE_NORMAL; + brd->SerialDriver.init_termios = DgncDefaultTermios; + brd->SerialDriver.driver_name = DRVSTR; + brd->SerialDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK); + + /* + * The kernel wants space to store pointers to + * tty_struct's and termios's. + */ + brd->SerialDriver.ttys = kcalloc(brd->maxports, sizeof(*brd->SerialDriver.ttys), GFP_KERNEL); + if (!brd->SerialDriver.ttys) + return -ENOMEM; + + kref_init(&brd->SerialDriver.kref); + brd->SerialDriver.termios = kcalloc(brd->maxports, sizeof(*brd->SerialDriver.termios), GFP_KERNEL); + if (!brd->SerialDriver.termios) + return -ENOMEM; + + /* + * Entry points for driver. Called by the kernel from + * tty_io.c and n_tty.c. + */ + tty_set_operations(&brd->SerialDriver, &dgnc_tty_ops); + + if (!brd->dgnc_Major_Serial_Registered) { + /* Register tty devices */ + rc = tty_register_driver(&brd->SerialDriver); + if (rc < 0) { + dev_dbg(&brd->pdev->dev, + "Can't register tty device (%d)\n", rc); + return rc; + } + brd->dgnc_Major_Serial_Registered = true; + } + + /* + * If we're doing transparent print, we have to do all of the above + * again, separately so we don't get the LD confused about what major + * we are when we get into the dgnc_tty_open() routine. + */ + brd->PrintDriver.magic = TTY_DRIVER_MAGIC; + snprintf(brd->PrintName, MAXTTYNAMELEN, "pr_dgnc_%d_", brd->boardnum); + + brd->PrintDriver.name = brd->PrintName; + brd->PrintDriver.name_base = 0; + brd->PrintDriver.major = brd->SerialDriver.major; + brd->PrintDriver.minor_start = 0x80; + brd->PrintDriver.num = brd->maxports; + brd->PrintDriver.type = TTY_DRIVER_TYPE_SERIAL; + brd->PrintDriver.subtype = SERIAL_TYPE_NORMAL; + brd->PrintDriver.init_termios = DgncDefaultTermios; + brd->PrintDriver.driver_name = DRVSTR; + brd->PrintDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK); + + /* + * The kernel wants space to store pointers to + * tty_struct's and termios's. Must be separated from + * the Serial Driver so we don't get confused + */ + brd->PrintDriver.ttys = kcalloc(brd->maxports, sizeof(*brd->PrintDriver.ttys), GFP_KERNEL); + if (!brd->PrintDriver.ttys) + return -ENOMEM; + kref_init(&brd->PrintDriver.kref); + brd->PrintDriver.termios = kcalloc(brd->maxports, sizeof(*brd->PrintDriver.termios), GFP_KERNEL); + if (!brd->PrintDriver.termios) + return -ENOMEM; + + /* + * Entry points for driver. Called by the kernel from + * tty_io.c and n_tty.c. + */ + tty_set_operations(&brd->PrintDriver, &dgnc_tty_ops); + + if (!brd->dgnc_Major_TransparentPrint_Registered) { + /* Register Transparent Print devices */ + rc = tty_register_driver(&brd->PrintDriver); + if (rc < 0) { + dev_dbg(&brd->pdev->dev, + "Can't register Transparent Print device(%d)\n", + rc); + return rc; + } + brd->dgnc_Major_TransparentPrint_Registered = true; + } + + dgnc_BoardsByMajor[brd->SerialDriver.major] = brd; + brd->dgnc_Serial_Major = brd->SerialDriver.major; + brd->dgnc_TransparentPrint_Major = brd->PrintDriver.major; + + return rc; +} + + +/* + * dgnc_tty_init() + * + * Init the tty subsystem. Called once per board after board has been + * downloaded and init'ed. + */ +int dgnc_tty_init(struct dgnc_board *brd) +{ + int i; + void __iomem *vaddr; + struct channel_t *ch; + + if (!brd) + return -ENXIO; + + /* + * Initialize board structure elements. + */ + + vaddr = brd->re_map_membase; + + brd->nasync = brd->maxports; + + /* + * Allocate channel memory that might not have been allocated + * when the driver was first loaded. + */ + for (i = 0; i < brd->nasync; i++) { + if (!brd->channels[i]) { + + /* + * Okay to malloc with GFP_KERNEL, we are not at + * interrupt context, and there are no locks held. + */ + brd->channels[i] = kzalloc(sizeof(*brd->channels[i]), GFP_KERNEL); + } + } + + ch = brd->channels[0]; + vaddr = brd->re_map_membase; + + /* Set up channel variables */ + for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) { + + if (!brd->channels[i]) + continue; + + spin_lock_init(&ch->ch_lock); + + /* Store all our magic numbers */ + ch->magic = DGNC_CHANNEL_MAGIC; + ch->ch_tun.magic = DGNC_UNIT_MAGIC; + ch->ch_tun.un_ch = ch; + ch->ch_tun.un_type = DGNC_SERIAL; + ch->ch_tun.un_dev = i; + + ch->ch_pun.magic = DGNC_UNIT_MAGIC; + ch->ch_pun.un_ch = ch; + ch->ch_pun.un_type = DGNC_PRINT; + ch->ch_pun.un_dev = i + 128; + + if (brd->bd_uart_offset == 0x200) + ch->ch_neo_uart = vaddr + (brd->bd_uart_offset * i); + else + ch->ch_cls_uart = vaddr + (brd->bd_uart_offset * i); + + ch->ch_bd = brd; + ch->ch_portnum = i; + ch->ch_digi = dgnc_digi_init; + + /* .25 second delay */ + ch->ch_close_delay = 250; + + init_waitqueue_head(&ch->ch_flags_wait); + init_waitqueue_head(&ch->ch_tun.un_flags_wait); + init_waitqueue_head(&ch->ch_pun.un_flags_wait); + + { + struct device *classp; + + classp = tty_register_device(&brd->SerialDriver, i, + &(ch->ch_bd->pdev->dev)); + ch->ch_tun.un_sysfs = classp; + dgnc_create_tty_sysfs(&ch->ch_tun, classp); + + classp = tty_register_device(&brd->PrintDriver, i, + &(ch->ch_bd->pdev->dev)); + ch->ch_pun.un_sysfs = classp; + dgnc_create_tty_sysfs(&ch->ch_pun, classp); + } + + } + + return 0; +} + + +/* + * dgnc_tty_post_uninit() + * + * UnInitialize any global tty related data. + */ +void dgnc_tty_post_uninit(void) +{ + kfree(dgnc_TmpWriteBuf); + dgnc_TmpWriteBuf = NULL; +} + + +/* + * dgnc_tty_uninit() + * + * Uninitialize the TTY portion of this driver. Free all memory and + * resources. + */ +void dgnc_tty_uninit(struct dgnc_board *brd) +{ + int i = 0; + + if (brd->dgnc_Major_Serial_Registered) { + dgnc_BoardsByMajor[brd->SerialDriver.major] = NULL; + brd->dgnc_Serial_Major = 0; + for (i = 0; i < brd->nasync; i++) { + dgnc_remove_tty_sysfs(brd->channels[i]->ch_tun.un_sysfs); + tty_unregister_device(&brd->SerialDriver, i); + } + tty_unregister_driver(&brd->SerialDriver); + brd->dgnc_Major_Serial_Registered = false; + } + + if (brd->dgnc_Major_TransparentPrint_Registered) { + dgnc_BoardsByMajor[brd->PrintDriver.major] = NULL; + brd->dgnc_TransparentPrint_Major = 0; + for (i = 0; i < brd->nasync; i++) { + dgnc_remove_tty_sysfs(brd->channels[i]->ch_pun.un_sysfs); + tty_unregister_device(&brd->PrintDriver, i); + } + tty_unregister_driver(&brd->PrintDriver); + brd->dgnc_Major_TransparentPrint_Registered = false; + } + + kfree(brd->SerialDriver.ttys); + brd->SerialDriver.ttys = NULL; + kfree(brd->PrintDriver.ttys); + brd->PrintDriver.ttys = NULL; +} + + +#define TMPBUFLEN (1024) + +/*======================================================================= + * + * dgnc_wmove - Write data to transmit queue. + * + * ch - Pointer to channel structure. + * buf - Poiter to characters to be moved. + * n - Number of characters to move. + * + *=======================================================================*/ +static void dgnc_wmove(struct channel_t *ch, char *buf, uint n) +{ + int remain; + uint head; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + head = ch->ch_w_head & WQUEUEMASK; + + /* + * If the write wraps over the top of the circular buffer, + * move the portion up to the wrap point, and reset the + * pointers to the bottom. + */ + remain = WQUEUESIZE - head; + + if (n >= remain) { + n -= remain; + memcpy(ch->ch_wqueue + head, buf, remain); + head = 0; + buf += remain; + } + + if (n > 0) { + /* + * Move rest of data. + */ + remain = n; + memcpy(ch->ch_wqueue + head, buf, remain); + head += remain; + } + + head &= WQUEUEMASK; + ch->ch_w_head = head; +} + + + + +/*======================================================================= + * + * dgnc_input - Process received data. + * + * ch - Pointer to channel structure. + * + *=======================================================================*/ +void dgnc_input(struct channel_t *ch) +{ + struct dgnc_board *bd; + struct tty_struct *tp; + struct tty_ldisc *ld = NULL; + uint rmask; + ushort head; + ushort tail; + int data_len; + unsigned long flags; + int flip_len; + int len = 0; + int n = 0; + int s = 0; + int i = 0; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + tp = ch->ch_tun.un_tty; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * Figure the number of characters in the buffer. + * Exit immediately if none. + */ + rmask = RQUEUEMASK; + head = ch->ch_r_head & rmask; + tail = ch->ch_r_tail & rmask; + data_len = (head - tail) & rmask; + + if (data_len == 0) + goto exit_unlock; + + /* + * If the device is not open, or CREAD is off, + * flush input data and return immediately. + */ + if (!tp || (tp->magic != TTY_MAGIC) || !(ch->ch_tun.un_flags & UN_ISOPEN) || + !(tp->termios.c_cflag & CREAD) || (ch->ch_tun.un_flags & UN_CLOSING)) { + + ch->ch_r_head = tail; + + /* Force queue flow control to be released, if needed */ + dgnc_check_queue_flow_control(ch); + + goto exit_unlock; + } + + /* + * If we are throttled, simply don't read any data. + */ + if (ch->ch_flags & CH_FORCED_STOPI) + goto exit_unlock; + + flip_len = TTY_FLIPBUF_SIZE; + + /* Chop down the length, if needed */ + len = min(data_len, flip_len); + len = min(len, (N_TTY_BUF_SIZE - 1)); + + ld = tty_ldisc_ref(tp); + +#ifdef TTY_DONT_FLIP + /* + * If the DONT_FLIP flag is on, don't flush our buffer, and act + * like the ld doesn't have any space to put the data right now. + */ + if (test_bit(TTY_DONT_FLIP, &tp->flags)) + len = 0; +#endif + + /* + * If we were unable to get a reference to the ld, + * don't flush our buffer, and act like the ld doesn't + * have any space to put the data right now. + */ + if (!ld) { + len = 0; + } else { + /* + * If ld doesn't have a pointer to a receive_buf function, + * flush the data, then act like the ld doesn't have any + * space to put the data right now. + */ + if (!ld->ops->receive_buf) { + ch->ch_r_head = ch->ch_r_tail; + len = 0; + } + } + + if (len <= 0) + goto exit_unlock; + + /* + * The tty layer in the kernel has changed in 2.6.16+. + * + * The flip buffers in the tty structure are no longer exposed, + * and probably will be going away eventually. + * + * If we are completely raw, we don't need to go through a lot + * of the tty layers that exist. + * In this case, we take the shortest and fastest route we + * can to relay the data to the user. + * + * On the other hand, if we are not raw, we need to go through + * the new 2.6.16+ tty layer, which has its API more well defined. + */ + len = tty_buffer_request_room(tp->port, len); + n = len; + + /* + * n now contains the most amount of data we can copy, + * bounded either by how much the Linux tty layer can handle, + * or the amount of data the card actually has pending... + */ + while (n) { + s = ((head >= tail) ? head : RQUEUESIZE) - tail; + s = min(s, n); + + if (s <= 0) + break; + + /* + * If conditions are such that ld needs to see all + * UART errors, we will have to walk each character + * and error byte and send them to the buffer one at + * a time. + */ + if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) { + for (i = 0; i < s; i++) { + if (*(ch->ch_equeue + tail + i) & UART_LSR_BI) + tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_BREAK); + else if (*(ch->ch_equeue + tail + i) & UART_LSR_PE) + tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_PARITY); + else if (*(ch->ch_equeue + tail + i) & UART_LSR_FE) + tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_FRAME); + else + tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_NORMAL); + } + } else { + tty_insert_flip_string(tp->port, ch->ch_rqueue + tail, s); + } + + tail += s; + n -= s; + /* Flip queue if needed */ + tail &= rmask; + } + + ch->ch_r_tail = tail & rmask; + ch->ch_e_tail = tail & rmask; + dgnc_check_queue_flow_control(ch); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* Tell the tty layer its okay to "eat" the data now */ + tty_flip_buffer_push(tp->port); + + if (ld) + tty_ldisc_deref(ld); + return; + +exit_unlock: + spin_unlock_irqrestore(&ch->ch_lock, flags); + if (ld) + tty_ldisc_deref(ld); +} + + +/************************************************************************ + * Determines when CARRIER changes state and takes appropriate + * action. + ************************************************************************/ +void dgnc_carrier(struct channel_t *ch) +{ + struct dgnc_board *bd; + + int virt_carrier = 0; + int phys_carrier = 0; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + if (ch->ch_mistat & UART_MSR_DCD) + phys_carrier = 1; + + if (ch->ch_digi.digi_flags & DIGI_FORCEDCD) + virt_carrier = 1; + + if (ch->ch_c_cflag & CLOCAL) + virt_carrier = 1; + + /* + * Test for a VIRTUAL carrier transition to HIGH. + */ + if (((ch->ch_flags & CH_FCAR) == 0) && (virt_carrier == 1)) { + + /* + * When carrier rises, wake any threads waiting + * for carrier in the open routine. + */ + + if (waitqueue_active(&(ch->ch_flags_wait))) + wake_up_interruptible(&ch->ch_flags_wait); + } + + /* + * Test for a PHYSICAL carrier transition to HIGH. + */ + if (((ch->ch_flags & CH_CD) == 0) && (phys_carrier == 1)) { + + /* + * When carrier rises, wake any threads waiting + * for carrier in the open routine. + */ + + if (waitqueue_active(&(ch->ch_flags_wait))) + wake_up_interruptible(&ch->ch_flags_wait); + } + + /* + * Test for a PHYSICAL transition to low, so long as we aren't + * currently ignoring physical transitions (which is what "virtual + * carrier" indicates). + * + * The transition of the virtual carrier to low really doesn't + * matter... it really only means "ignore carrier state", not + * "make pretend that carrier is there". + */ + if ((virt_carrier == 0) && ((ch->ch_flags & CH_CD) != 0) && + (phys_carrier == 0)) { + + /* + * When carrier drops: + * + * Drop carrier on all open units. + * + * Flush queues, waking up any task waiting in the + * line discipline. + * + * Send a hangup to the control terminal. + * + * Enable all select calls. + */ + if (waitqueue_active(&(ch->ch_flags_wait))) + wake_up_interruptible(&ch->ch_flags_wait); + + if (ch->ch_tun.un_open_count > 0) + tty_hangup(ch->ch_tun.un_tty); + + if (ch->ch_pun.un_open_count > 0) + tty_hangup(ch->ch_pun.un_tty); + } + + /* + * Make sure that our cached values reflect the current reality. + */ + if (virt_carrier == 1) + ch->ch_flags |= CH_FCAR; + else + ch->ch_flags &= ~CH_FCAR; + + if (phys_carrier == 1) + ch->ch_flags |= CH_CD; + else + ch->ch_flags &= ~CH_CD; +} + +/* + * Assign the custom baud rate to the channel structure + */ +static void dgnc_set_custom_speed(struct channel_t *ch, uint newrate) +{ + int testdiv; + int testrate_high; + int testrate_low; + int deltahigh; + int deltalow; + + if (newrate <= 0) { + ch->ch_custom_speed = 0; + return; + } + + /* + * Since the divisor is stored in a 16-bit integer, we make sure + * we don't allow any rates smaller than a 16-bit integer would allow. + * And of course, rates above the dividend won't fly. + */ + if (newrate && newrate < ((ch->ch_bd->bd_dividend / 0xFFFF) + 1)) + newrate = ((ch->ch_bd->bd_dividend / 0xFFFF) + 1); + + if (newrate && newrate > ch->ch_bd->bd_dividend) + newrate = ch->ch_bd->bd_dividend; + + if (newrate > 0) { + testdiv = ch->ch_bd->bd_dividend / newrate; + + /* + * If we try to figure out what rate the board would use + * with the test divisor, it will be either equal or higher + * than the requested baud rate. If we then determine the + * rate with a divisor one higher, we will get the next lower + * supported rate below the requested. + */ + testrate_high = ch->ch_bd->bd_dividend / testdiv; + testrate_low = ch->ch_bd->bd_dividend / (testdiv + 1); + + /* + * If the rate for the requested divisor is correct, just + * use it and be done. + */ + if (testrate_high != newrate) { + /* + * Otherwise, pick the rate that is closer (i.e. whichever rate + * has a smaller delta). + */ + deltahigh = testrate_high - newrate; + deltalow = newrate - testrate_low; + + if (deltahigh < deltalow) + newrate = testrate_high; + else + newrate = testrate_low; + } + } + + ch->ch_custom_speed = newrate; +} + + +void dgnc_check_queue_flow_control(struct channel_t *ch) +{ + int qleft = 0; + + /* Store how much space we have left in the queue */ + qleft = ch->ch_r_tail - ch->ch_r_head - 1; + if (qleft < 0) + qleft += RQUEUEMASK + 1; + + /* + * Check to see if we should enforce flow control on our queue because + * the ld (or user) isn't reading data out of our queue fast enuf. + * + * NOTE: This is done based on what the current flow control of the + * port is set for. + * + * 1) HWFLOW (RTS) - Turn off the UART's Receive interrupt. + * This will cause the UART's FIFO to back up, and force + * the RTS signal to be dropped. + * 2) SWFLOW (IXOFF) - Keep trying to send a stop character to + * the other side, in hopes it will stop sending data to us. + * 3) NONE - Nothing we can do. We will simply drop any extra data + * that gets sent into us when the queue fills up. + */ + if (qleft < 256) { + /* HWFLOW */ + if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) { + if (!(ch->ch_flags & CH_RECEIVER_OFF)) { + ch->ch_bd->bd_ops->disable_receiver(ch); + ch->ch_flags |= (CH_RECEIVER_OFF); + } + } + /* SWFLOW */ + else if (ch->ch_c_iflag & IXOFF) { + if (ch->ch_stops_sent <= MAX_STOPS_SENT) { + ch->ch_bd->bd_ops->send_stop_character(ch); + ch->ch_stops_sent++; + } + } + } + + /* + * Check to see if we should unenforce flow control because + * ld (or user) finally read enuf data out of our queue. + * + * NOTE: This is done based on what the current flow control of the + * port is set for. + * + * 1) HWFLOW (RTS) - Turn back on the UART's Receive interrupt. + * This will cause the UART's FIFO to raise RTS back up, + * which will allow the other side to start sending data again. + * 2) SWFLOW (IXOFF) - Send a start character to + * the other side, so it will start sending data to us again. + * 3) NONE - Do nothing. Since we didn't do anything to turn off the + * other side, we don't need to do anything now. + */ + if (qleft > (RQUEUESIZE / 2)) { + /* HWFLOW */ + if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) { + if (ch->ch_flags & CH_RECEIVER_OFF) { + ch->ch_bd->bd_ops->enable_receiver(ch); + ch->ch_flags &= ~(CH_RECEIVER_OFF); + } + } + /* SWFLOW */ + else if (ch->ch_c_iflag & IXOFF && ch->ch_stops_sent) { + ch->ch_stops_sent = 0; + ch->ch_bd->bd_ops->send_start_character(ch); + } + /* No FLOW */ + else { + /* Nothing needed. */ + } + } +} + + +void dgnc_wakeup_writes(struct channel_t *ch) +{ + int qlen = 0; + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * If channel now has space, wake up anyone waiting on the condition. + */ + qlen = ch->ch_w_head - ch->ch_w_tail; + if (qlen < 0) + qlen += WQUEUESIZE; + + if (qlen >= (WQUEUESIZE - 256)) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return; + } + + if (ch->ch_tun.un_flags & UN_ISOPEN) { + if ((ch->ch_tun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + ch->ch_tun.un_tty->ldisc->ops->write_wakeup) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + (ch->ch_tun.un_tty->ldisc->ops->write_wakeup)(ch->ch_tun.un_tty); + spin_lock_irqsave(&ch->ch_lock, flags); + } + + wake_up_interruptible(&ch->ch_tun.un_tty->write_wait); + + /* + * If unit is set to wait until empty, check to make sure + * the queue AND FIFO are both empty. + */ + if (ch->ch_tun.un_flags & UN_EMPTY) { + if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) { + ch->ch_tun.un_flags &= ~(UN_EMPTY); + + /* + * If RTS Toggle mode is on, whenever + * the queue and UART is empty, keep RTS low. + */ + if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) { + ch->ch_mostat &= ~(UART_MCR_RTS); + ch->ch_bd->bd_ops->assert_modem_signals(ch); + } + + /* + * If DTR Toggle mode is on, whenever + * the queue and UART is empty, keep DTR low. + */ + if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) { + ch->ch_mostat &= ~(UART_MCR_DTR); + ch->ch_bd->bd_ops->assert_modem_signals(ch); + } + } + } + + wake_up_interruptible(&ch->ch_tun.un_flags_wait); + } + + if (ch->ch_pun.un_flags & UN_ISOPEN) { + if ((ch->ch_pun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + ch->ch_pun.un_tty->ldisc->ops->write_wakeup) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + (ch->ch_pun.un_tty->ldisc->ops->write_wakeup)(ch->ch_pun.un_tty); + spin_lock_irqsave(&ch->ch_lock, flags); + } + + wake_up_interruptible(&ch->ch_pun.un_tty->write_wait); + + /* + * If unit is set to wait until empty, check to make sure + * the queue AND FIFO are both empty. + */ + if (ch->ch_pun.un_flags & UN_EMPTY) { + if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) + ch->ch_pun.un_flags &= ~(UN_EMPTY); + } + + wake_up_interruptible(&ch->ch_pun.un_flags_wait); + } + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + + +/************************************************************************ + * + * TTY Entry points and helper functions + * + ************************************************************************/ + +/* + * dgnc_tty_open() + * + */ +static int dgnc_tty_open(struct tty_struct *tty, struct file *file) +{ + struct dgnc_board *brd; + struct channel_t *ch; + struct un_t *un; + uint major = 0; + uint minor = 0; + int rc = 0; + unsigned long flags; + + rc = 0; + + major = MAJOR(tty_devnum(tty)); + minor = MINOR(tty_devnum(tty)); + + if (major > 255) + return -ENXIO; + + /* Get board pointer from our array of majors we have allocated */ + brd = dgnc_BoardsByMajor[major]; + if (!brd) + return -ENXIO; + + /* + * If board is not yet up to a state of READY, go to + * sleep waiting for it to happen or they cancel the open. + */ + rc = wait_event_interruptible(brd->state_wait, + (brd->state & BOARD_READY)); + + if (rc) + return rc; + + spin_lock_irqsave(&brd->bd_lock, flags); + + /* If opened device is greater than our number of ports, bail. */ + if (PORT_NUM(minor) >= brd->nasync) { + spin_unlock_irqrestore(&brd->bd_lock, flags); + return -ENXIO; + } + + ch = brd->channels[PORT_NUM(minor)]; + if (!ch) { + spin_unlock_irqrestore(&brd->bd_lock, flags); + return -ENXIO; + } + + /* Drop board lock */ + spin_unlock_irqrestore(&brd->bd_lock, flags); + + /* Grab channel lock */ + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Figure out our type */ + if (!IS_PRINT(minor)) { + un = &brd->channels[PORT_NUM(minor)]->ch_tun; + un->un_type = DGNC_SERIAL; + } else if (IS_PRINT(minor)) { + un = &brd->channels[PORT_NUM(minor)]->ch_pun; + un->un_type = DGNC_PRINT; + } else { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return -ENXIO; + } + + /* + * If the port is still in a previous open, and in a state + * where we simply cannot safely keep going, wait until the + * state clears. + */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + + rc = wait_event_interruptible(ch->ch_flags_wait, ((ch->ch_flags & CH_OPENING) == 0)); + + /* If ret is non-zero, user ctrl-c'ed us */ + if (rc) + return -EINTR; + + /* + * If either unit is in the middle of the fragile part of close, + * we just cannot touch the channel safely. + * Go to sleep, knowing that when the channel can be + * touched safely, the close routine will signal the + * ch_flags_wait to wake us back up. + */ + rc = wait_event_interruptible(ch->ch_flags_wait, + (((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING) == 0)); + + /* If ret is non-zero, user ctrl-c'ed us */ + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + + + /* Store our unit into driver_data, so we always have it available. */ + tty->driver_data = un; + + + /* + * Initialize tty's + */ + if (!(un->un_flags & UN_ISOPEN)) { + /* Store important variables. */ + un->un_tty = tty; + + /* Maybe do something here to the TTY struct as well? */ + } + + + /* + * Allocate channel buffers for read/write/error. + * Set flag, so we don't get trounced on. + */ + ch->ch_flags |= (CH_OPENING); + + /* Drop locks, as malloc with GFP_KERNEL can sleep */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (!ch->ch_rqueue) + ch->ch_rqueue = kzalloc(RQUEUESIZE, GFP_KERNEL); + if (!ch->ch_equeue) + ch->ch_equeue = kzalloc(EQUEUESIZE, GFP_KERNEL); + if (!ch->ch_wqueue) + ch->ch_wqueue = kzalloc(WQUEUESIZE, GFP_KERNEL); + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags &= ~(CH_OPENING); + wake_up_interruptible(&ch->ch_flags_wait); + + /* + * Initialize if neither terminal or printer is open. + */ + if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_ISOPEN)) { + + /* + * Flush input queues. + */ + ch->ch_r_head = 0; + ch->ch_r_tail = 0; + ch->ch_e_head = 0; + ch->ch_e_tail = 0; + ch->ch_w_head = 0; + ch->ch_w_tail = 0; + + brd->bd_ops->flush_uart_write(ch); + brd->bd_ops->flush_uart_read(ch); + + ch->ch_flags = 0; + ch->ch_cached_lsr = 0; + ch->ch_stop_sending_break = 0; + ch->ch_stops_sent = 0; + + ch->ch_c_cflag = tty->termios.c_cflag; + ch->ch_c_iflag = tty->termios.c_iflag; + ch->ch_c_oflag = tty->termios.c_oflag; + ch->ch_c_lflag = tty->termios.c_lflag; + ch->ch_startc = tty->termios.c_cc[VSTART]; + ch->ch_stopc = tty->termios.c_cc[VSTOP]; + + /* + * Bring up RTS and DTR... + * Also handle RTS or DTR toggle if set. + */ + if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat |= (UART_MCR_RTS); + if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat |= (UART_MCR_DTR); + + /* Tell UART to init itself */ + brd->bd_ops->uart_init(ch); + } + + /* + * Run param in case we changed anything + */ + brd->bd_ops->param(tty); + + dgnc_carrier(ch); + + /* + * follow protocol for opening port + */ + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + rc = dgnc_block_til_ready(tty, file, ch); + + /* No going back now, increment our unit and channel counters */ + spin_lock_irqsave(&ch->ch_lock, flags); + ch->ch_open_count++; + un->un_open_count++; + un->un_flags |= (UN_ISOPEN); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return rc; +} + + +/* + * dgnc_block_til_ready() + * + * Wait for DCD, if needed. + */ +static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch) +{ + int retval = 0; + struct un_t *un = NULL; + unsigned long flags; + uint old_flags = 0; + int sleep_on_un_flags = 0; + + if (!tty || tty->magic != TTY_MAGIC || !file || !ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENXIO; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -ENXIO; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_wopen++; + + /* Loop forever */ + while (1) { + + sleep_on_un_flags = 0; + + /* + * If board has failed somehow during our sleep, bail with error. + */ + if (ch->ch_bd->state == BOARD_FAILED) { + retval = -ENXIO; + break; + } + + /* If tty was hung up, break out of loop and set error. */ + if (tty_hung_up_p(file)) { + retval = -EAGAIN; + break; + } + + /* + * If either unit is in the middle of the fragile part of close, + * we just cannot touch the channel safely. + * Go back to sleep, knowing that when the channel can be + * touched safely, the close routine will signal the + * ch_wait_flags to wake us back up. + */ + if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING)) { + + /* + * Our conditions to leave cleanly and happily: + * 1) NONBLOCKING on the tty is set. + * 2) CLOCAL is set. + * 3) DCD (fake or real) is active. + */ + + if (file->f_flags & O_NONBLOCK) + break; + + if (tty->flags & (1 << TTY_IO_ERROR)) { + retval = -EIO; + break; + } + + if (ch->ch_flags & CH_CD) + break; + + if (ch->ch_flags & CH_FCAR) + break; + } else { + sleep_on_un_flags = 1; + } + + /* + * If there is a signal pending, the user probably + * interrupted (ctrl-c) us. + * Leave loop with error set. + */ + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + + /* + * Store the flags before we let go of channel lock + */ + if (sleep_on_un_flags) + old_flags = ch->ch_tun.un_flags | ch->ch_pun.un_flags; + else + old_flags = ch->ch_flags; + + /* + * Let go of channel lock before calling schedule. + * Our poller will get any FEP events and wake us up when DCD + * eventually goes active. + */ + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* + * Wait for something in the flags to change from the current value. + */ + if (sleep_on_un_flags) + retval = wait_event_interruptible(un->un_flags_wait, + (old_flags != (ch->ch_tun.un_flags | ch->ch_pun.un_flags))); + else + retval = wait_event_interruptible(ch->ch_flags_wait, + (old_flags != ch->ch_flags)); + + /* + * We got woken up for some reason. + * Before looping around, grab our channel lock. + */ + spin_lock_irqsave(&ch->ch_lock, flags); + } + + ch->ch_wopen--; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (retval) + return retval; + + return 0; +} + + +/* + * dgnc_tty_hangup() + * + * Hangup the port. Like a close, but don't wait for output to drain. + */ +static void dgnc_tty_hangup(struct tty_struct *tty) +{ + struct un_t *un; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + /* flush the transmit queues */ + dgnc_tty_flush_buffer(tty); + +} + + +/* + * dgnc_tty_close() + * + */ +static void dgnc_tty_close(struct tty_struct *tty, struct file *file) +{ + struct ktermios *ts; + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + int rc = 0; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + ts = &tty->termios; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * Determine if this is the last close or not - and if we agree about + * which type of close it is with the Line Discipline + */ + if ((tty->count == 1) && (un->un_open_count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. un_open_count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + dev_dbg(tty->dev, + "tty->count is 1, un open count is %d\n", + un->un_open_count); + un->un_open_count = 1; + } + + if (un->un_open_count) + un->un_open_count--; + else + dev_dbg(tty->dev, + "bad serial port open count of %d\n", + un->un_open_count); + + ch->ch_open_count--; + + if (ch->ch_open_count && un->un_open_count) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return; + } + + /* OK, its the last close on the unit */ + un->un_flags |= UN_CLOSING; + + tty->closing = 1; + + + /* + * Only officially close channel if count is 0 and + * DIGI_PRINTER bit is not set. + */ + if ((ch->ch_open_count == 0) && !(ch->ch_digi.digi_flags & DIGI_PRINTER)) { + + ch->ch_flags &= ~(CH_STOPI | CH_FORCED_STOPI); + + /* + * turn off print device when closing print device. + */ + if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) { + dgnc_wmove(ch, ch->ch_digi.digi_offstr, + (int) ch->ch_digi.digi_offlen); + ch->ch_flags &= ~CH_PRON; + } + + spin_unlock_irqrestore(&ch->ch_lock, flags); + /* wait for output to drain */ + /* This will also return if we take an interrupt */ + + rc = bd->bd_ops->drain(tty, 0); + + dgnc_tty_flush_buffer(tty); + tty_ldisc_flush(tty); + + spin_lock_irqsave(&ch->ch_lock, flags); + + tty->closing = 0; + + /* + * If we have HUPCL set, lower DTR and RTS + */ + if (ch->ch_c_cflag & HUPCL) { + + /* Drop RTS/DTR */ + ch->ch_mostat &= ~(UART_MCR_DTR | UART_MCR_RTS); + bd->bd_ops->assert_modem_signals(ch); + + /* + * Go to sleep to ensure RTS/DTR + * have been dropped for modems to see it. + */ + if (ch->ch_close_delay) { + spin_unlock_irqrestore(&ch->ch_lock, + flags); + dgnc_ms_sleep(ch->ch_close_delay); + spin_lock_irqsave(&ch->ch_lock, flags); + } + } + + ch->ch_old_baud = 0; + + /* Turn off UART interrupts for this port */ + ch->ch_bd->bd_ops->uart_off(ch); + } else { + /* + * turn off print device when closing print device. + */ + if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) { + dgnc_wmove(ch, ch->ch_digi.digi_offstr, + (int) ch->ch_digi.digi_offlen); + ch->ch_flags &= ~CH_PRON; + } + } + + un->un_tty = NULL; + un->un_flags &= ~(UN_ISOPEN | UN_CLOSING); + + wake_up_interruptible(&ch->ch_flags_wait); + wake_up_interruptible(&un->un_flags_wait); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +/* + * dgnc_tty_chars_in_buffer() + * + * Return number of characters that have not been transmitted yet. + * + * This routine is used by the line discipline to determine if there + * is data waiting to be transmitted/drained/flushed or not. + */ +static int dgnc_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct channel_t *ch = NULL; + struct un_t *un = NULL; + ushort thead; + ushort ttail; + uint tmask; + uint chars = 0; + unsigned long flags; + + if (tty == NULL) + return 0; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + + spin_lock_irqsave(&ch->ch_lock, flags); + + tmask = WQUEUEMASK; + thead = ch->ch_w_head & tmask; + ttail = ch->ch_w_tail & tmask; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (ttail == thead) { + chars = 0; + } else { + if (thead >= ttail) + chars = thead - ttail; + else + chars = thead - ttail + WQUEUESIZE; + } + + return chars; +} + + +/* + * dgnc_maxcps_room + * + * Reduces bytes_available to the max number of characters + * that can be sent currently given the maxcps value, and + * returns the new bytes_available. This only affects printer + * output. + */ +static int dgnc_maxcps_room(struct tty_struct *tty, int bytes_available) +{ + struct channel_t *ch = NULL; + struct un_t *un = NULL; + + if (!tty) + return bytes_available; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return bytes_available; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return bytes_available; + + /* + * If its not the Transparent print device, return + * the full data amount. + */ + if (un->un_type != DGNC_PRINT) + return bytes_available; + + if (ch->ch_digi.digi_maxcps > 0 && ch->ch_digi.digi_bufsize > 0) { + int cps_limit = 0; + unsigned long current_time = jiffies; + unsigned long buffer_time = current_time + + (HZ * ch->ch_digi.digi_bufsize) / ch->ch_digi.digi_maxcps; + + if (ch->ch_cpstime < current_time) { + /* buffer is empty */ + ch->ch_cpstime = current_time; /* reset ch_cpstime */ + cps_limit = ch->ch_digi.digi_bufsize; + } else if (ch->ch_cpstime < buffer_time) { + /* still room in the buffer */ + cps_limit = ((buffer_time - ch->ch_cpstime) * ch->ch_digi.digi_maxcps) / HZ; + } else { + /* no room in the buffer */ + cps_limit = 0; + } + + bytes_available = min(cps_limit, bytes_available); + } + + return bytes_available; +} + + +/* + * dgnc_tty_write_room() + * + * Return space available in Tx buffer + */ +static int dgnc_tty_write_room(struct tty_struct *tty) +{ + struct channel_t *ch = NULL; + struct un_t *un = NULL; + ushort head; + ushort tail; + ushort tmask; + int ret = 0; + unsigned long flags; + + if (tty == NULL || dgnc_TmpWriteBuf == NULL) + return 0; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + + spin_lock_irqsave(&ch->ch_lock, flags); + + tmask = WQUEUEMASK; + head = (ch->ch_w_head) & tmask; + tail = (ch->ch_w_tail) & tmask; + + ret = tail - head - 1; + if (ret < 0) + ret += WQUEUESIZE; + + /* Limit printer to maxcps */ + ret = dgnc_maxcps_room(tty, ret); + + /* + * If we are printer device, leave space for + * possibly both the on and off strings. + */ + if (un->un_type == DGNC_PRINT) { + if (!(ch->ch_flags & CH_PRON)) + ret -= ch->ch_digi.digi_onlen; + ret -= ch->ch_digi.digi_offlen; + } else { + if (ch->ch_flags & CH_PRON) + ret -= ch->ch_digi.digi_offlen; + } + + if (ret < 0) + ret = 0; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return ret; +} + + +/* + * dgnc_tty_put_char() + * + * Put a character into ch->ch_buf + * + * - used by the line discipline for OPOST processing + */ +static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c) +{ + /* + * Simply call tty_write. + */ + dgnc_tty_write(tty, &c, 1); + return 1; +} + + +/* + * dgnc_tty_write() + * + * Take data from the user or kernel and send it out to the FEP. + * In here exists all the Transparent Print magic as well. + */ +static int dgnc_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct channel_t *ch = NULL; + struct un_t *un = NULL; + int bufcount = 0, n = 0; + int orig_count = 0; + unsigned long flags; + ushort head; + ushort tail; + ushort tmask; + uint remain; + int from_user = 0; + + if (tty == NULL || dgnc_TmpWriteBuf == NULL) + return 0; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + + if (!count) + return 0; + + /* + * Store original amount of characters passed in. + * This helps to figure out if we should ask the FEP + * to send us an event when it has more space available. + */ + orig_count = count; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Get our space available for the channel from the board */ + tmask = WQUEUEMASK; + head = (ch->ch_w_head) & tmask; + tail = (ch->ch_w_tail) & tmask; + + bufcount = tail - head - 1; + if (bufcount < 0) + bufcount += WQUEUESIZE; + + /* + * Limit printer output to maxcps overall, with bursts allowed + * up to bufsize characters. + */ + bufcount = dgnc_maxcps_room(tty, bufcount); + + /* + * Take minimum of what the user wants to send, and the + * space available in the FEP buffer. + */ + count = min(count, bufcount); + + /* + * Bail if no space left. + */ + if (count <= 0) + goto exit_retry; + + /* + * Output the printer ON string, if we are in terminal mode, but + * need to be in printer mode. + */ + if ((un->un_type == DGNC_PRINT) && !(ch->ch_flags & CH_PRON)) { + dgnc_wmove(ch, ch->ch_digi.digi_onstr, + (int) ch->ch_digi.digi_onlen); + head = (ch->ch_w_head) & tmask; + ch->ch_flags |= CH_PRON; + } + + /* + * On the other hand, output the printer OFF string, if we are + * currently in printer mode, but need to output to the terminal. + */ + if ((un->un_type != DGNC_PRINT) && (ch->ch_flags & CH_PRON)) { + dgnc_wmove(ch, ch->ch_digi.digi_offstr, + (int) ch->ch_digi.digi_offlen); + head = (ch->ch_w_head) & tmask; + ch->ch_flags &= ~CH_PRON; + } + + /* + * If there is nothing left to copy, or I can't handle any more data, leave. + */ + if (count <= 0) + goto exit_retry; + + if (from_user) { + + count = min(count, WRITEBUFLEN); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* + * If data is coming from user space, copy it into a temporary + * buffer so we don't get swapped out while doing the copy to + * the board. + */ + /* we're allowed to block if it's from_user */ + if (down_interruptible(&dgnc_TmpWriteSem)) + return -EINTR; + + /* + * copy_from_user() returns the number + * of bytes that could *NOT* be copied. + */ + count -= copy_from_user(dgnc_TmpWriteBuf, (const unsigned char __user *) buf, count); + + if (!count) { + up(&dgnc_TmpWriteSem); + return -EFAULT; + } + + spin_lock_irqsave(&ch->ch_lock, flags); + + buf = dgnc_TmpWriteBuf; + + } + + n = count; + + /* + * If the write wraps over the top of the circular buffer, + * move the portion up to the wrap point, and reset the + * pointers to the bottom. + */ + remain = WQUEUESIZE - head; + + if (n >= remain) { + n -= remain; + memcpy(ch->ch_wqueue + head, buf, remain); + head = 0; + buf += remain; + } + + if (n > 0) { + /* + * Move rest of data. + */ + remain = n; + memcpy(ch->ch_wqueue + head, buf, remain); + head += remain; + } + + if (count) { + head &= tmask; + ch->ch_w_head = head; + } + + /* Update printer buffer empty time. */ + if ((un->un_type == DGNC_PRINT) && (ch->ch_digi.digi_maxcps > 0) + && (ch->ch_digi.digi_bufsize > 0)) { + ch->ch_cpstime += (HZ * count) / ch->ch_digi.digi_maxcps; + } + + if (from_user) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + up(&dgnc_TmpWriteSem); + } else { + spin_unlock_irqrestore(&ch->ch_lock, flags); + } + + if (count) { + /* + * Channel lock is grabbed and then released + * inside this routine. + */ + ch->ch_bd->bd_ops->copy_data_from_queue_to_uart(ch); + } + + return count; + +exit_retry: + + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; +} + + +/* + * Return modem signals to ld. + */ + +static int dgnc_tty_tiocmget(struct tty_struct *tty) +{ + struct channel_t *ch; + struct un_t *un; + int result = -EIO; + unsigned char mstat = 0; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return result; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return result; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return result; + + spin_lock_irqsave(&ch->ch_lock, flags); + + mstat = (ch->ch_mostat | ch->ch_mistat); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + result = 0; + + if (mstat & UART_MCR_DTR) + result |= TIOCM_DTR; + if (mstat & UART_MCR_RTS) + result |= TIOCM_RTS; + if (mstat & UART_MSR_CTS) + result |= TIOCM_CTS; + if (mstat & UART_MSR_DSR) + result |= TIOCM_DSR; + if (mstat & UART_MSR_RI) + result |= TIOCM_RI; + if (mstat & UART_MSR_DCD) + result |= TIOCM_CD; + + return result; +} + + +/* + * dgnc_tty_tiocmset() + * + * Set modem signals, called by ld. + */ + +static int dgnc_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int ret = -EIO; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return ret; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return ret; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return ret; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return ret; + + spin_lock_irqsave(&ch->ch_lock, flags); + + if (set & TIOCM_RTS) + ch->ch_mostat |= UART_MCR_RTS; + + if (set & TIOCM_DTR) + ch->ch_mostat |= UART_MCR_DTR; + + if (clear & TIOCM_RTS) + ch->ch_mostat &= ~(UART_MCR_RTS); + + if (clear & TIOCM_DTR) + ch->ch_mostat &= ~(UART_MCR_DTR); + + ch->ch_bd->bd_ops->assert_modem_signals(ch); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; +} + + +/* + * dgnc_tty_send_break() + * + * Send a Break, called by ld. + */ +static int dgnc_tty_send_break(struct tty_struct *tty, int msec) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int ret = -EIO; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return ret; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return ret; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return ret; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return ret; + + switch (msec) { + case -1: + msec = 0xFFFF; + break; + case 0: + msec = 0; + break; + default: + break; + } + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_bd->bd_ops->send_break(ch, msec); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + +} + + +/* + * dgnc_tty_wait_until_sent() + * + * wait until data has been transmitted, called by ld. + */ +static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int rc; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + rc = bd->bd_ops->drain(tty, 0); +} + + +/* + * dgnc_send_xchar() + * + * send a high priority character, called by ld. + */ +static void dgnc_tty_send_xchar(struct tty_struct *tty, char c) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + dev_dbg(tty->dev, "dgnc_tty_send_xchar start\n"); + + spin_lock_irqsave(&ch->ch_lock, flags); + bd->bd_ops->send_immediate_char(ch, c); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + dev_dbg(tty->dev, "dgnc_tty_send_xchar finish\n"); +} + + + + +/* + * Return modem signals to ld. + */ +static inline int dgnc_get_mstat(struct channel_t *ch) +{ + unsigned char mstat; + int result = -EIO; + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENXIO; + + spin_lock_irqsave(&ch->ch_lock, flags); + + mstat = (ch->ch_mostat | ch->ch_mistat); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + result = 0; + + if (mstat & UART_MCR_DTR) + result |= TIOCM_DTR; + if (mstat & UART_MCR_RTS) + result |= TIOCM_RTS; + if (mstat & UART_MSR_CTS) + result |= TIOCM_CTS; + if (mstat & UART_MSR_DSR) + result |= TIOCM_DSR; + if (mstat & UART_MSR_RI) + result |= TIOCM_RI; + if (mstat & UART_MSR_DCD) + result |= TIOCM_CD; + + return result; +} + + + +/* + * Return modem signals to ld. + */ +static int dgnc_get_modem_info(struct channel_t *ch, unsigned int __user *value) +{ + int result; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENXIO; + + result = dgnc_get_mstat(ch); + + if (result < 0) + return -ENXIO; + + return put_user(result, value); +} + + +/* + * dgnc_set_modem_info() + * + * Set modem signals, called by ld. + */ +static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int ret = -ENXIO; + unsigned int arg = 0; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return ret; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return ret; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return ret; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return ret; + + ret = get_user(arg, value); + if (ret) + return ret; + + switch (command) { + case TIOCMBIS: + if (arg & TIOCM_RTS) + ch->ch_mostat |= UART_MCR_RTS; + + if (arg & TIOCM_DTR) + ch->ch_mostat |= UART_MCR_DTR; + + break; + + case TIOCMBIC: + if (arg & TIOCM_RTS) + ch->ch_mostat &= ~(UART_MCR_RTS); + + if (arg & TIOCM_DTR) + ch->ch_mostat &= ~(UART_MCR_DTR); + + break; + + case TIOCMSET: + + if (arg & TIOCM_RTS) + ch->ch_mostat |= UART_MCR_RTS; + else + ch->ch_mostat &= ~(UART_MCR_RTS); + + if (arg & TIOCM_DTR) + ch->ch_mostat |= UART_MCR_DTR; + else + ch->ch_mostat &= ~(UART_MCR_DTR); + + break; + + default: + return -EINVAL; + } + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_bd->bd_ops->assert_modem_signals(ch); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; +} + + +/* + * dgnc_tty_digigeta() + * + * Ioctl to get the information for ditty. + * + * + * + */ +static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo) +{ + struct channel_t *ch; + struct un_t *un; + struct digi_t tmp; + unsigned long flags; + + if (!retinfo) + return -EFAULT; + + if (!tty || tty->magic != TTY_MAGIC) + return -EFAULT; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -EFAULT; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + + spin_lock_irqsave(&ch->ch_lock, flags); + memcpy(&tmp, &ch->ch_digi, sizeof(tmp)); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + + return 0; +} + + +/* + * dgnc_tty_digiseta() + * + * Ioctl to set the information for ditty. + * + * + * + */ +static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + struct digi_t new_digi; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return -EFAULT; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -EFAULT; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -EFAULT; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return -EFAULT; + + if (copy_from_user(&new_digi, new_info, sizeof(new_digi))) + return -EFAULT; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * Handle transistions to and from RTS Toggle. + */ + if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && (new_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat &= ~(UART_MCR_RTS); + if ((ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && !(new_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat |= (UART_MCR_RTS); + + /* + * Handle transistions to and from DTR Toggle. + */ + if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && (new_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat &= ~(UART_MCR_DTR); + if ((ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && !(new_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat |= (UART_MCR_DTR); + + memcpy(&ch->ch_digi, &new_digi, sizeof(new_digi)); + + if (ch->ch_digi.digi_maxcps < 1) + ch->ch_digi.digi_maxcps = 1; + + if (ch->ch_digi.digi_maxcps > 10000) + ch->ch_digi.digi_maxcps = 10000; + + if (ch->ch_digi.digi_bufsize < 10) + ch->ch_digi.digi_bufsize = 10; + + if (ch->ch_digi.digi_maxchar < 1) + ch->ch_digi.digi_maxchar = 1; + + if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize) + ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize; + + if (ch->ch_digi.digi_onlen > DIGI_PLEN) + ch->ch_digi.digi_onlen = DIGI_PLEN; + + if (ch->ch_digi.digi_offlen > DIGI_PLEN) + ch->ch_digi.digi_offlen = DIGI_PLEN; + + ch->ch_bd->bd_ops->param(tty); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; +} + + +/* + * dgnc_set_termios() + */ +static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_c_cflag = tty->termios.c_cflag; + ch->ch_c_iflag = tty->termios.c_iflag; + ch->ch_c_oflag = tty->termios.c_oflag; + ch->ch_c_lflag = tty->termios.c_lflag; + ch->ch_startc = tty->termios.c_cc[VSTART]; + ch->ch_stopc = tty->termios.c_cc[VSTOP]; + + ch->ch_bd->bd_ops->param(tty); + dgnc_carrier(ch); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +static void dgnc_tty_throttle(struct tty_struct *tty) +{ + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags |= (CH_FORCED_STOPI); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +static void dgnc_tty_unthrottle(struct tty_struct *tty) +{ + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags &= ~(CH_FORCED_STOPI); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +static void dgnc_tty_start(struct tty_struct *tty) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags &= ~(CH_FORCED_STOP); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +static void dgnc_tty_stop(struct tty_struct *tty) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags |= (CH_FORCED_STOP); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +/* + * dgnc_tty_flush_chars() + * + * Flush the cook buffer + * + * Note to self, and any other poor souls who venture here: + * + * flush in this case DOES NOT mean dispose of the data. + * instead, it means "stop buffering and send it if you + * haven't already." Just guess how I figured that out... SRW 2-Jun-98 + * + * It is also always called in interrupt context - JAR 8-Sept-99 + */ +static void dgnc_tty_flush_chars(struct tty_struct *tty) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Do something maybe here */ + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + + +/* + * dgnc_tty_flush_buffer() + * + * Flush Tx buffer (make in == out) + */ +static void dgnc_tty_flush_buffer(struct tty_struct *tty) +{ + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags &= ~CH_STOP; + + /* Flush our write queue */ + ch->ch_w_head = ch->ch_w_tail; + + /* Flush UARTs transmit FIFO */ + ch->ch_bd->bd_ops->flush_uart_write(ch); + + if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) { + ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY); + wake_up_interruptible(&ch->ch_tun.un_flags_wait); + } + if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) { + ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY); + wake_up_interruptible(&ch->ch_pun.un_flags_wait); + } + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + + +/***************************************************************************** + * + * The IOCTL function and all of its helpers + * + *****************************************************************************/ + +/* + * dgnc_tty_ioctl() + * + * The usual assortment of ioctl's + */ +static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int rc; + unsigned long flags; + void __user *uarg = (void __user *) arg; + + if (!tty || tty->magic != TTY_MAGIC) + return -ENODEV; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -ENODEV; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENODEV; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return -ENODEV; + + spin_lock_irqsave(&ch->ch_lock, flags); + + if (un->un_open_count <= 0) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return -EIO; + } + + switch (cmd) { + + /* Here are all the standard ioctl's that we MUST implement */ + + case TCSBRK: + /* + * TCSBRK is SVID version: non-zero arg --> no break + * this behaviour is exploited by tcdrain(). + * + * According to POSIX.1 spec (7.2.2.1.2) breaks should be + * between 0.25 and 0.5 seconds so we'll ask for something + * in the middle: 0.375 seconds. + */ + rc = tty_check_change(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + if (rc) + return rc; + + rc = ch->ch_bd->bd_ops->drain(tty, 0); + + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + + if (((cmd == TCSBRK) && (!arg)) || (cmd == TCSBRKP)) + ch->ch_bd->bd_ops->send_break(ch, 250); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + + + case TCSBRKP: + /* support for POSIX tcsendbreak() + * According to POSIX.1 spec (7.2.2.1.2) breaks should be + * between 0.25 and 0.5 seconds so we'll ask for something + * in the middle: 0.375 seconds. + */ + rc = tty_check_change(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + if (rc) + return rc; + + rc = ch->ch_bd->bd_ops->drain(tty, 0); + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_bd->bd_ops->send_break(ch, 250); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + + case TIOCSBRK: + rc = tty_check_change(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + if (rc) + return rc; + + rc = ch->ch_bd->bd_ops->drain(tty, 0); + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_bd->bd_ops->send_break(ch, 250); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + + case TIOCCBRK: + /* Do Nothing */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; + + case TIOCGSOFTCAR: + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + rc = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg); + return rc; + + case TIOCSSOFTCAR: + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = get_user(arg, (unsigned long __user *) arg); + if (rc) + return rc; + + spin_lock_irqsave(&ch->ch_lock, flags); + tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0)); + ch->ch_bd->bd_ops->param(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + + case TIOCMGET: + spin_unlock_irqrestore(&ch->ch_lock, flags); + return dgnc_get_modem_info(ch, uarg); + + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + spin_unlock_irqrestore(&ch->ch_lock, flags); + return dgnc_set_modem_info(tty, cmd, uarg); + + /* + * Here are any additional ioctl's that we want to implement + */ + + case TCFLSH: + /* + * The linux tty driver doesn't have a flush + * input routine for the driver, assuming all backed + * up data is in the line disc. buffers. However, + * we all know that's not the case. Here, we + * act on the ioctl, but then lie and say we didn't + * so the line discipline will process the flush + * also. + */ + rc = tty_check_change(tty); + if (rc) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return rc; + } + + if ((arg == TCIFLUSH) || (arg == TCIOFLUSH)) { + ch->ch_r_head = ch->ch_r_tail; + ch->ch_bd->bd_ops->flush_uart_read(ch); + /* Force queue flow control to be released, if needed */ + dgnc_check_queue_flow_control(ch); + } + + if ((arg == TCOFLUSH) || (arg == TCIOFLUSH)) { + if (!(un->un_type == DGNC_PRINT)) { + ch->ch_w_head = ch->ch_w_tail; + ch->ch_bd->bd_ops->flush_uart_write(ch); + + if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) { + ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY); + wake_up_interruptible(&ch->ch_tun.un_flags_wait); + } + + if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) { + ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY); + wake_up_interruptible(&ch->ch_pun.un_flags_wait); + } + + } + } + + /* pretend we didn't recognize this IOCTL */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + return -ENOIOCTLCMD; + case TCSETSF: + case TCSETSW: + /* + * The linux tty driver doesn't have a flush + * input routine for the driver, assuming all backed + * up data is in the line disc. buffers. However, + * we all know that's not the case. Here, we + * act on the ioctl, but then lie and say we didn't + * so the line discipline will process the flush + * also. + */ + if (cmd == TCSETSF) { + /* flush rx */ + ch->ch_flags &= ~CH_STOP; + ch->ch_r_head = ch->ch_r_tail; + ch->ch_bd->bd_ops->flush_uart_read(ch); + /* Force queue flow control to be released, if needed */ + dgnc_check_queue_flow_control(ch); + } + + /* now wait for all the output to drain */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = ch->ch_bd->bd_ops->drain(tty, 0); + if (rc) + return -EINTR; + + /* pretend we didn't recognize this */ + return -ENOIOCTLCMD; + + case TCSETAW: + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = ch->ch_bd->bd_ops->drain(tty, 0); + if (rc) + return -EINTR; + + /* pretend we didn't recognize this */ + return -ENOIOCTLCMD; + + case TCXONC: + spin_unlock_irqrestore(&ch->ch_lock, flags); + /* Make the ld do it */ + return -ENOIOCTLCMD; + + case DIGI_GETA: + /* get information for ditty */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + return dgnc_tty_digigeta(tty, uarg); + + case DIGI_SETAW: + case DIGI_SETAF: + + /* set information for ditty */ + if (cmd == (DIGI_SETAW)) { + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = ch->ch_bd->bd_ops->drain(tty, 0); + + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + } else { + tty_ldisc_flush(tty); + } + /* fall thru */ + + case DIGI_SETA: + spin_unlock_irqrestore(&ch->ch_lock, flags); + return dgnc_tty_digiseta(tty, uarg); + + case DIGI_LOOPBACK: + { + uint loopback = 0; + /* Let go of locks when accessing user space, could sleep */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = get_user(loopback, (unsigned int __user *) arg); + if (rc) + return rc; + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Enable/disable internal loopback for this port */ + if (loopback) + ch->ch_flags |= CH_LOOPBACK; + else + ch->ch_flags &= ~(CH_LOOPBACK); + + ch->ch_bd->bd_ops->param(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; + } + + case DIGI_GETCUSTOMBAUD: + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = put_user(ch->ch_custom_speed, (unsigned int __user *) arg); + return rc; + + case DIGI_SETCUSTOMBAUD: + { + int new_rate; + /* Let go of locks when accessing user space, could sleep */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = get_user(new_rate, (int __user *) arg); + if (rc) + return rc; + spin_lock_irqsave(&ch->ch_lock, flags); + dgnc_set_custom_speed(ch, new_rate); + ch->ch_bd->bd_ops->param(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; + } + + /* + * This ioctl allows insertion of a character into the front + * of any pending data to be transmitted. + * + * This ioctl is to satify the "Send Character Immediate" + * call that the RealPort protocol spec requires. + */ + case DIGI_REALPORT_SENDIMMEDIATE: + { + unsigned char c; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = get_user(c, (unsigned char __user *) arg); + if (rc) + return rc; + spin_lock_irqsave(&ch->ch_lock, flags); + ch->ch_bd->bd_ops->send_immediate_char(ch, c); + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; + } + + /* + * This ioctl returns all the current counts for the port. + * + * This ioctl is to satify the "Line Error Counters" + * call that the RealPort protocol spec requires. + */ + case DIGI_REALPORT_GETCOUNTERS: + { + struct digi_getcounter buf; + + buf.norun = ch->ch_err_overrun; + buf.noflow = 0; /* The driver doesn't keep this stat */ + buf.nframe = ch->ch_err_frame; + buf.nparity = ch->ch_err_parity; + buf.nbreak = ch->ch_err_break; + buf.rbytes = ch->ch_rxcount; + buf.tbytes = ch->ch_txcount; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (copy_to_user(uarg, &buf, sizeof(buf))) + return -EFAULT; + + return 0; + } + + /* + * This ioctl returns all current events. + * + * This ioctl is to satify the "Event Reporting" + * call that the RealPort protocol spec requires. + */ + case DIGI_REALPORT_GETEVENTS: + { + unsigned int events = 0; + + /* NOTE: MORE EVENTS NEEDS TO BE ADDED HERE */ + if (ch->ch_flags & CH_BREAK_SENDING) + events |= EV_TXB; + if ((ch->ch_flags & CH_STOP) || (ch->ch_flags & CH_FORCED_STOP)) + events |= (EV_OPU | EV_OPS); + + if ((ch->ch_flags & CH_STOPI) || (ch->ch_flags & CH_FORCED_STOPI)) + events |= (EV_IPU | EV_IPS); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = put_user(events, (unsigned int __user *) arg); + return rc; + } + + /* + * This ioctl returns TOUT and TIN counters based + * upon the values passed in by the RealPort Server. + * It also passes back whether the UART Transmitter is + * empty as well. + */ + case DIGI_REALPORT_GETBUFFERS: + { + struct digi_getbuffer buf; + int tdist; + int count; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* + * Get data from user first. + */ + if (copy_from_user(&buf, uarg, sizeof(buf))) + return -EFAULT; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * Figure out how much data is in our RX and TX queues. + */ + buf.rxbuf = (ch->ch_r_head - ch->ch_r_tail) & RQUEUEMASK; + buf.txbuf = (ch->ch_w_head - ch->ch_w_tail) & WQUEUEMASK; + + /* + * Is the UART empty? Add that value to whats in our TX queue. + */ + count = buf.txbuf + ch->ch_bd->bd_ops->get_uart_bytes_left(ch); + + /* + * Figure out how much data the RealPort Server believes should + * be in our TX queue. + */ + tdist = (buf.tIn - buf.tOut) & 0xffff; + + /* + * If we have more data than the RealPort Server believes we + * should have, reduce our count to its amount. + * + * This count difference CAN happen because the Linux LD can + * insert more characters into our queue for OPOST processing + * that the RealPort Server doesn't know about. + */ + if (buf.txbuf > tdist) + buf.txbuf = tdist; + + /* + * Report whether our queue and UART TX are completely empty. + */ + if (count) + buf.txdone = 0; + else + buf.txdone = 1; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (copy_to_user(uarg, &buf, sizeof(buf))) + return -EFAULT; + + return 0; + } + default: + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return -ENOIOCTLCMD; + } +} -- cgit v1.2.3-54-g00ecf