diff options
Diffstat (limited to 'drivers/staging/dgrp/dgrp_tty.c')
-rw-r--r-- | drivers/staging/dgrp/dgrp_tty.c | 3341 |
1 files changed, 3341 insertions, 0 deletions
diff --git a/drivers/staging/dgrp/dgrp_tty.c b/drivers/staging/dgrp/dgrp_tty.c new file mode 100644 index 000000000000..e125b03598d7 --- /dev/null +++ b/drivers/staging/dgrp/dgrp_tty.c @@ -0,0 +1,3341 @@ +/* + * + * Copyright 1999 Digi International (www.digi.com) + * Gene Olson <Gene_Olson at digi dot com> + * James Puzzo <jamesp at digi dot com> + * Jeff Randall + * Scott Kilau <scottk at digi dot 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; 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. + * + */ + +/* + * + * Filename: + * + * dgrp_tty.c + * + * Description: + * + * This file implements the tty driver functionality for the + * RealPort driver software. + * + * Author: + * + * James A. Puzzo + * Ann-Marie Westgate + * + */ + +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/sched.h> +#include <linux/uaccess.h> + +#include "dgrp_common.h" + +#ifndef _POSIX_VDISABLE +#define _POSIX_VDISABLE ('\0') +#endif + +/* + * forward declarations + */ + +static void drp_param(struct ch_struct *); +static void dgrp_tty_close(struct tty_struct *, struct file *); + +/* ioctl helper functions */ +static int set_modem_info(struct ch_struct *, unsigned int, unsigned int *); +static int get_modem_info(struct ch_struct *, unsigned int *); +static void dgrp_set_custom_speed(struct ch_struct *, int); +static int dgrp_tty_digigetedelay(struct tty_struct *, int *); +static int dgrp_tty_digisetedelay(struct tty_struct *, int *); +static int dgrp_send_break(struct ch_struct *, int); + +static ushort tty_to_ch_flags(struct tty_struct *, char); +static tcflag_t ch_to_tty_flags(unsigned short, char); + +static void dgrp_tty_input_start(struct tty_struct *); +static void dgrp_tty_input_stop(struct tty_struct *); + +static void drp_wmove(struct ch_struct *, int, void*, int); + +static int dgrp_tty_open(struct tty_struct *, struct file *); +static void dgrp_tty_close(struct tty_struct *, struct file *); +static int dgrp_tty_write(struct tty_struct *, const unsigned char *, int); +static int dgrp_tty_write_room(struct tty_struct *); +static void dgrp_tty_flush_buffer(struct tty_struct *); +static int dgrp_tty_chars_in_buffer(struct tty_struct *); +static int dgrp_tty_ioctl(struct tty_struct *, unsigned int, unsigned long); +static void dgrp_tty_set_termios(struct tty_struct *, struct ktermios *); +static void dgrp_tty_stop(struct tty_struct *); +static void dgrp_tty_start(struct tty_struct *); +static void dgrp_tty_throttle(struct tty_struct *); +static void dgrp_tty_unthrottle(struct tty_struct *); +static void dgrp_tty_hangup(struct tty_struct *); +static int dgrp_tty_put_char(struct tty_struct *, unsigned char); +static int dgrp_tty_tiocmget(struct tty_struct *); +static int dgrp_tty_tiocmset(struct tty_struct *, unsigned int, unsigned int); +static int dgrp_tty_send_break(struct tty_struct *, int); +static void dgrp_tty_send_xchar(struct tty_struct *, char); + +/* + * tty defines + */ +#define SERIAL_TYPE_NORMAL 1 +#define SERIAL_TYPE_CALLOUT 2 +#define SERIAL_TYPE_XPRINT 3 + + +/* + * tty globals/statics + */ + + +#define PORTSERVER_DIVIDEND 1843200 + +/* + * Default transparent print information. + */ +static struct digi_struct 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 DefaultTermios = { + .c_iflag = (ICRNL | IXON), + .c_oflag = (OPOST | ONLCR), + .c_cflag = (B9600 | CS8 | CREAD | HUPCL | CLOCAL), + .c_lflag = (ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL + | ECHOKE | IEXTEN), + .c_cc = INIT_C_CC, + .c_line = 0, +}; + +/* Define our tty operations struct */ +static const struct tty_operations dgrp_tty_ops = { + .open = dgrp_tty_open, + .close = dgrp_tty_close, + .write = dgrp_tty_write, + .write_room = dgrp_tty_write_room, + .flush_buffer = dgrp_tty_flush_buffer, + .chars_in_buffer = dgrp_tty_chars_in_buffer, + .flush_chars = NULL, + .ioctl = dgrp_tty_ioctl, + .set_termios = dgrp_tty_set_termios, + .stop = dgrp_tty_stop, + .start = dgrp_tty_start, + .throttle = dgrp_tty_throttle, + .unthrottle = dgrp_tty_unthrottle, + .hangup = dgrp_tty_hangup, + .put_char = dgrp_tty_put_char, + .tiocmget = dgrp_tty_tiocmget, + .tiocmset = dgrp_tty_tiocmset, + .break_ctl = dgrp_tty_send_break, + .send_xchar = dgrp_tty_send_xchar +}; + + +static int calc_baud_rate(struct un_struct *un) +{ + int i; + int brate; + + struct baud_rates { + unsigned int rate; + unsigned int cflag; + }; + + static struct baud_rates baud_rates[] = { + { 921600, B921600 }, + { 460800, B460800 }, + { 230400, B230400 }, + { 115200, B115200 }, + { 57600, B57600 }, + { 38400, B38400 }, + { 19200, B19200 }, + { 9600, B9600 }, + { 4800, B4800 }, + { 2400, B2400 }, + { 1200, B1200 }, + { 600, B600 }, + { 300, B300 }, + { 200, B200 }, + { 150, B150 }, + { 134, B134 }, + { 110, B110 }, + { 75, B75 }, + { 50, B50 }, + { 0, B9600 } + }; + + brate = C_BAUD(un->un_tty); + + for (i = 0; baud_rates[i].rate; i++) { + if (baud_rates[i].cflag == brate) + break; + } + + return baud_rates[i].rate; +} + +static int calc_fastbaud_rate(struct un_struct *un, struct ktermios *uts) +{ + int i; + int brate; + + ulong bauds[2][16] = { + { /* fastbaud*/ + 0, 57600, 76800, 115200, + 131657, 153600, 230400, 460800, + 921600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }, + { /* fastbaud & CBAUDEX */ + 0, 57600, 115200, 230400, + 460800, 150, 200, 921600, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 } + }; + + brate = C_BAUD(un->un_tty) & 0xff; + + i = (uts->c_cflag & CBAUDEX) ? 1 : 0; + + + if ((i >= 0) && (i < 2) && (brate >= 0) && (brate < 16)) + brate = bauds[i][brate]; + else + brate = 0; + + return brate; +} + +/** + * drp_param() -- send parameter values to be sent to the node + * @ch: channel structure of port to modify + * + * Interprets the tty and modem changes made by an application + * program (by examining the termios structures) and sets up + * parameter values to be sent to the node. + */ +static void drp_param(struct ch_struct *ch) +{ + struct nd_struct *nd; + struct un_struct *un; + int brate; + int mflow; + int xflag; + int iflag; + struct ktermios *tts, *pts, *uts; + + nd = ch->ch_nd; + + /* + * If the terminal device is open, use it to set up all tty + * modes and functions. Otherwise use the printer device. + */ + + if (ch->ch_tun.un_open_count) { + + un = &ch->ch_tun; + tts = &ch->ch_tun.un_tty->termios; + + /* + * If both devices are open, copy critical line + * parameters from the tty device to the printer, + * so that if the tty is closed, the printer will + * continue without disruption. + */ + + if (ch->ch_pun.un_open_count) { + + pts = &ch->ch_pun.un_tty->termios; + + pts->c_cflag ^= + (pts->c_cflag ^ tts->c_cflag) & + (CBAUD | CSIZE | CSTOPB | CREAD | PARENB | + PARODD | HUPCL | CLOCAL); + + pts->c_iflag ^= + (pts->c_iflag ^ tts->c_iflag) & + (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | + ISTRIP | IXON | IXANY | IXOFF); + + pts->c_cc[VSTART] = tts->c_cc[VSTART]; + pts->c_cc[VSTOP] = tts->c_cc[VSTOP]; + } + } else if (ch->ch_pun.un_open_count == 0) { + pr_warn("%s - ch_pun.un_open_count shouldn't be 0\n", + __func__); + return; + } else { + un = &ch->ch_pun; + } + + uts = &un->un_tty->termios; + + /* + * Determine if FAST writes can be performed. + */ + + if ((ch->ch_digi.digi_flags & DIGI_COOK) != 0 && + (ch->ch_tun.un_open_count != 0) && + !((un->un_tty)->ldisc->ops->flags & LDISC_FLAG_DEFINED) && + !(L_XCASE(un->un_tty))) { + ch->ch_flag |= CH_FAST_WRITE; + } else { + ch->ch_flag &= ~CH_FAST_WRITE; + } + + /* + * If FAST writes can be performed, and OPOST is on in the + * terminal device, do OPOST handling in the server. + */ + + if ((ch->ch_flag & CH_FAST_WRITE) && + O_OPOST(un->un_tty) != 0) { + int oflag = tty_to_ch_flags(un->un_tty, 'o'); + + /* add to ch_ocook any processing flags set in the termio */ + ch->ch_ocook |= oflag & (OF_OLCUC | + OF_ONLCR | + OF_OCRNL | + OF_ONLRET | + OF_TABDLY); + + /* + * the hpux driver clears any flags set in ch_ocook + * from the termios oflag. It is STILL reported though + * by a TCGETA + */ + + oflag = ch_to_tty_flags(ch->ch_ocook, 'o'); + uts->c_oflag &= ~oflag; + + } else { + /* clear the ch->ch_ocook flag */ + int oflag = ch_to_tty_flags(ch->ch_ocook, 'o'); + uts->c_oflag |= oflag; + ch->ch_ocook = 0; + } + + ch->ch_oflag = ch->ch_ocook; + + + ch->ch_flag &= ~CH_FAST_READ; + + /* + * Generate channel flags + */ + + if (C_BAUD(un->un_tty) == B0) { + if (!(ch->ch_flag & CH_BAUD0)) { + /* TODO : the HPUX driver flushes line */ + /* TODO : discipline, I assume I don't have to */ + + ch->ch_tout = ch->ch_tin; + ch->ch_rout = ch->ch_rin; + + ch->ch_break_time = 0; + + ch->ch_send |= RR_TX_FLUSH | RR_RX_FLUSH; + + ch->ch_mout &= ~(DM_DTR | DM_RTS); + + ch->ch_flag |= CH_BAUD0; + } + } else if (ch->ch_custom_speed) { + ch->ch_brate = PORTSERVER_DIVIDEND / ch->ch_custom_speed ; + + if (ch->ch_flag & CH_BAUD0) { + ch->ch_mout |= DM_DTR | DM_RTS; + + ch->ch_flag &= ~CH_BAUD0; + } + } else { + /* + * Baud rate mapping. + * + * If FASTBAUD isn't on, we can scan the new baud rate list + * as required. + * + * However, if FASTBAUD is on, we must go to the old + * baud rate mapping that existed many many moons ago, + * for compatibility reasons. + */ + + if (!(ch->ch_digi.digi_flags & DIGI_FAST)) + brate = calc_baud_rate(un); + else + brate = calc_fastbaud_rate(un, uts); + + if (brate == 0) + brate = 9600; + + ch->ch_brate = PORTSERVER_DIVIDEND / brate; + + if (ch->ch_flag & CH_BAUD0) { + ch->ch_mout |= DM_DTR | DM_RTS; + + ch->ch_flag &= ~CH_BAUD0; + } + } + + /* + * Generate channel cflags from the termio. + */ + + ch->ch_cflag = tty_to_ch_flags(un->un_tty, 'c'); + + /* + * Generate channel iflags from the termio. + */ + + iflag = (int) tty_to_ch_flags(un->un_tty, 'i'); + + if (START_CHAR(un->un_tty) == _POSIX_VDISABLE || + STOP_CHAR(un->un_tty) == _POSIX_VDISABLE) { + iflag &= ~(IF_IXON | IF_IXANY | IF_IXOFF); + } + + ch->ch_iflag = iflag; + + /* + * Generate flow control characters + */ + + /* + * From the POSIX.1 spec (7.1.2.6): "If {_POSIX_VDISABLE} + * is defined for the terminal device file, and the value + * of one of the changable special control characters (see + * 7.1.1.9) is {_POSIX_VDISABLE}, that function shall be + * disabled, that is, no input data shall be recognized as + * the disabled special character." + * + * OK, so we don't ever assign S/DXB XON or XOFF to _POSIX_VDISABLE. + */ + + if (uts->c_cc[VSTART] != _POSIX_VDISABLE) + ch->ch_xon = uts->c_cc[VSTART]; + if (uts->c_cc[VSTOP] != _POSIX_VDISABLE) + ch->ch_xoff = uts->c_cc[VSTOP]; + + ch->ch_lnext = (uts->c_cc[VLNEXT] == _POSIX_VDISABLE ? 0 : + uts->c_cc[VLNEXT]); + + /* + * Also, if either c_cc[START] or c_cc[STOP] is set to + * _POSIX_VDISABLE, we can't really do software flow + * control--in either direction--so we turn it off as + * far as S/DXB is concerned. In essence, if you disable + * one, you disable the other too. + */ + if ((uts->c_cc[VSTART] == _POSIX_VDISABLE) || + (uts->c_cc[VSTOP] == _POSIX_VDISABLE)) + ch->ch_iflag &= ~(IF_IXOFF | IF_IXON); + + /* + * Update xflags. + */ + + xflag = 0; + + if (ch->ch_digi.digi_flags & DIGI_AIXON) + xflag = XF_XIXON; + + if ((ch->ch_xxon == _POSIX_VDISABLE) || + (ch->ch_xxoff == _POSIX_VDISABLE)) + xflag &= ~XF_XIXON; + + ch->ch_xflag = xflag; + + + /* + * Figure effective DCD value. + */ + + if (C_CLOCAL(un->un_tty)) + ch->ch_flag |= CH_CLOCAL; + else + ch->ch_flag &= ~CH_CLOCAL; + + /* + * Check modem signals + */ + + dgrp_carrier(ch); + + /* + * Get hardware handshake value. + */ + + mflow = 0; + + if (C_CRTSCTS(un->un_tty)) + mflow |= (DM_RTS | DM_CTS); + + if (ch->ch_digi.digi_flags & RTSPACE) + mflow |= DM_RTS; + + if (ch->ch_digi.digi_flags & DTRPACE) + mflow |= DM_DTR; + + if (ch->ch_digi.digi_flags & CTSPACE) + mflow |= DM_CTS; + + if (ch->ch_digi.digi_flags & DSRPACE) + mflow |= DM_DSR; + + if (ch->ch_digi.digi_flags & DCDPACE) + mflow |= DM_CD; + + if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) + mflow |= DM_RTS_TOGGLE; + + ch->ch_mflow = mflow; + + /* + * Send the changes to the server. + */ + + ch->ch_flag |= CH_PARAM; + (ch->ch_nd)->nd_tx_work = 1; + + if (waitqueue_active(&ch->ch_flag_wait)) + wake_up_interruptible(&ch->ch_flag_wait); +} + +/* + * This function is just used as a callback for timeouts + * waiting on the ch_sleep flag. + */ +static void wake_up_drp_sleep_timer(unsigned long ptr) +{ + struct ch_struct *ch = (struct ch_struct *) ptr; + if (ch) + wake_up(&ch->ch_sleep); +} + + +/* + * Set up our own sleep that can't be cancelled + * until our timeout occurs. + */ +static void drp_my_sleep(struct ch_struct *ch) +{ + struct timer_list drp_wakeup_timer; + DECLARE_WAITQUEUE(wait, current); + + /* + * First make sure we're ready to receive the wakeup. + */ + + add_wait_queue(&ch->ch_sleep, &wait); + current->state = TASK_UNINTERRUPTIBLE; + + /* + * Since we are uninterruptible, set a timer to + * unset the uninterruptable state in 1 second. + */ + + init_timer(&drp_wakeup_timer); + drp_wakeup_timer.function = wake_up_drp_sleep_timer; + drp_wakeup_timer.data = (unsigned long) ch; + drp_wakeup_timer.expires = jiffies + (1 * HZ); + add_timer(&drp_wakeup_timer); + + schedule(); + + del_timer(&drp_wakeup_timer); + + remove_wait_queue(&ch->ch_sleep, &wait); +} + +/* + * dgrp_tty_open() + * + * returns: + * -EBUSY - this is a callout device and the normal device is active + * - there is an error in opening the tty + * -ENODEV - the channel does not exist + * -EAGAIN - we are in the middle of hanging up or closing + * - IMMEDIATE_OPEN fails + * -ENXIO or -EAGAIN + * - if the port is outside physical range + * -EINTR - the open is interrupted + * + */ +static int dgrp_tty_open(struct tty_struct *tty, struct file *file) +{ + int retval = 0; + struct nd_struct *nd; + struct ch_struct *ch; + struct un_struct *un; + int port; + int delay_error; + int otype; + int unf; + int wait_carrier; + int category; + int counts_were_incremented = 0; + ulong lock_flags; + DECLARE_WAITQUEUE(wait, current); + + /* + * Do some initial checks to see if the node and port exist + */ + + nd = nd_struct_get(MAJOR(tty_devnum(tty))); + port = PORT_NUM(MINOR(tty_devnum(tty))); + category = OPEN_CATEGORY(MINOR(tty_devnum(tty))); + + if (!nd) + return -ENODEV; + + if (port >= CHAN_MAX) + return -ENODEV; + + /* + * The channel exists. + */ + + ch = nd->nd_chan + port; + + un = IS_PRINT(MINOR(tty_devnum(tty))) ? &ch->ch_pun : &ch->ch_tun; + un->un_tty = tty; + tty->driver_data = un; + + /* + * If we are in the middle of hanging up, + * then return an error + */ + if (tty_hung_up_p(file)) { + retval = ((un->un_flag & UN_HUP_NOTIFY) ? + -EAGAIN : -ERESTARTSYS); + goto done; + } + + /* + * If the port is in the middle of closing, then block + * until it is done, then try again. + */ + retval = wait_event_interruptible(un->un_close_wait, + ((un->un_flag & UN_CLOSING) == 0)); + + if (retval) + goto done; + + /* + * If the port is in the middle of a reopen after a network disconnect, + * wait until it is done, then try again. + */ + retval = wait_event_interruptible(ch->ch_flag_wait, + ((ch->ch_flag & CH_PORT_GONE) == 0)); + + if (retval) + goto done; + + /* + * If this is a callout device, then just make sure the normal + * device isn't being used. + */ + + if (tty->driver->subtype == SERIAL_TYPE_CALLOUT) { + if (un->un_flag & UN_NORMAL_ACTIVE) { + retval = -EBUSY; + goto done; + } else { + un->un_flag |= UN_CALLOUT_ACTIVE; + } + } + + /* + * Loop waiting until the open can be successfully completed. + */ + + spin_lock_irqsave(&nd->nd_lock, lock_flags); + + nd->nd_tx_work = 1; + + for (;;) { + wait_carrier = 0; + + /* + * Determine the open type from the flags provided. + */ + + /* + * If the port is not enabled, then exit + */ + if (test_bit(TTY_IO_ERROR, &tty->flags)) { + /* there was an error in opening the tty */ + if (un->un_flag & UN_CALLOUT_ACTIVE) + retval = -EBUSY; + else + un->un_flag |= UN_NORMAL_ACTIVE; + goto unlock; + } + + if (file->f_flags & O_NONBLOCK) { + + /* + * if the O_NONBLOCK is set, errors on read and write + * must return -EAGAIN immediately and NOT sleep + * on the waitqs. + */ + otype = OTYPE_IMMEDIATE; + delay_error = -EAGAIN; + + } else if (!OPEN_WAIT_AVAIL(category) || + (file->f_flags & O_NDELAY) != 0) { + otype = OTYPE_IMMEDIATE; + delay_error = -EBUSY; + + } else if (!OPEN_WAIT_CARRIER(category) || + ((ch->ch_digi.digi_flags & DIGI_FORCEDCD) != 0) || + C_CLOCAL(tty)) { + otype = OTYPE_PERSISTENT; + delay_error = 0; + + } else { + otype = OTYPE_INCOMING; + delay_error = 0; + } + + /* + * Handle port currently outside physical port range. + */ + + if (port >= nd->nd_chan_count) { + if (otype == OTYPE_IMMEDIATE) { + retval = (nd->nd_state == NS_READY) ? + -ENXIO : -EAGAIN; + goto unlock; + } + } + + /* + * Handle port not currently open. + */ + + else if (ch->ch_open_count == 0) { + /* + * Return an error when an Incoming Open + * response indicates the port is busy. + */ + + if (ch->ch_open_error != 0 && otype == ch->ch_otype) { + retval = (ch->ch_open_error <= 2) ? + delay_error : -ENXIO ; + goto unlock; + } + + /* + * Fail any new Immediate open if we do not have + * a normal connection to the server. + */ + + if (nd->nd_state != NS_READY && + otype == OTYPE_IMMEDIATE) { + retval = -EAGAIN; + goto unlock; + } + + /* + * If a Realport open of the correct type has + * succeeded, complete the open. + */ + + if (ch->ch_state == CS_READY && ch->ch_otype == otype) + break; + } + + /* + * Handle port already open and active as a device + * of same category. + */ + + else if ((ch->ch_category == category) || + IS_PRINT(MINOR(tty_devnum(tty)))) { + /* + * Fail if opening the device now would + * violate exclusive use. + */ + unf = ch->ch_tun.un_flag | ch->ch_pun.un_flag; + + if ((file->f_flags & O_EXCL) || (unf & UN_EXCL)) { + retval = -EBUSY; + goto unlock; + } + + /* + * If the open device is in the hangup state, all + * system calls fail except close(). + */ + + /* TODO : check on hangup_p calls */ + + if (ch->ch_flag & CH_HANGUP) { + retval = -ENXIO; + goto unlock; + } + + /* + * If the port is ready, and carrier is ignored + * or present, then complete the open. + */ + + if (ch->ch_state == CS_READY && + (otype != OTYPE_INCOMING || + ch->ch_flag & CH_VIRT_CD)) + break; + + wait_carrier = 1; + } + + /* + * Handle port active with a different category device. + */ + + else { + if (otype == OTYPE_IMMEDIATE) { + retval = delay_error; + goto unlock; + } + } + + /* + * Wait until conditions change, then take another + * try at the open. + */ + + ch->ch_wait_count[otype]++; + + if (wait_carrier) + ch->ch_wait_carrier++; + + /* + * Prepare the task to accept the wakeup, then + * release our locks and release control. + */ + + add_wait_queue(&ch->ch_flag_wait, &wait); + current->state = TASK_INTERRUPTIBLE; + + spin_unlock_irqrestore(&nd->nd_lock, lock_flags); + + /* + * Give up control, we'll come back if we're + * interrupted or are woken up. + */ + schedule(); + remove_wait_queue(&ch->ch_flag_wait, &wait); + + spin_lock_irqsave(&nd->nd_lock, lock_flags); + + current->state = TASK_RUNNING; + + ch->ch_wait_count[otype]--; + + if (wait_carrier) + ch->ch_wait_carrier--; + + nd->nd_tx_work = 1; + + if (signal_pending(current)) { + retval = -EINTR; + goto unlock; + } + } /* end for(;;) */ + + /* + * The open has succeeded. No turning back. + */ + counts_were_incremented = 1; + un->un_open_count++; + ch->ch_open_count++; + + /* + * Initialize the channel, if it's not already open. + */ + + if (ch->ch_open_count == 1) { + ch->ch_flag = 0; + ch->ch_inwait = 0; + ch->ch_category = category; + ch->ch_pscan_state = 0; + + /* TODO : find out what PS-1 bug Gene was referring to */ + /* TODO : in the following comment. */ + + ch->ch_send = RR_TX_START | RR_RX_START; /* PS-1 bug */ + + if (C_CLOCAL(tty) || + ch->ch_s_mlast & DM_CD || + ch->ch_digi.digi_flags & DIGI_FORCEDCD) + ch->ch_flag |= CH_VIRT_CD; + else if (OPEN_FORCES_CARRIER(category)) + ch->ch_flag |= CH_VIRT_CD; + + } + + /* + * Initialize the unit, if it is not already open. + */ + + if (un->un_open_count == 1) { + /* + * Since all terminal options are always sticky in Linux, + * we don't need the UN_STICKY flag to be handled specially. + */ + /* clears all the digi flags, leaves serial flags */ + un->un_flag &= ~UN_DIGI_MASK; + + if (file->f_flags & O_EXCL) + un->un_flag |= UN_EXCL; + + /* TODO : include "session" and "pgrp" */ + + /* + * In Linux, all terminal parameters are intended to be sticky. + * as a result, we "remove" the code which once reset the ports + * to sane values. + */ + + drp_param(ch); + + } + + un->un_flag |= UN_INITIALIZED; + + retval = 0; + +unlock: + + spin_unlock_irqrestore(&nd->nd_lock, lock_flags); + +done: + /* + * Linux does a close for every open, even failed ones! + */ + if (!counts_were_incremented) { + un->un_open_count++; + ch->ch_open_count++; + } + + if (retval) + dev_err(tty->dev, "tty open bad return (%i)\n", retval); + + return retval; +} + + + + +/* + * dgrp_tty_close() -- close function for tty_operations + */ +static void dgrp_tty_close(struct tty_struct *tty, struct file *file) +{ + struct ch_struct *ch; + struct un_struct *un; + struct nd_struct *nd; + int tpos; + int port; + int err = 0; + int s = 0; + ulong waketime; + ulong lock_flags; + int sent_printer_offstr = 0; + + port = PORT_NUM(MINOR(tty_devnum(tty))); + + un = tty->driver_data; + + if (!un) + return; + + ch = un->un_ch; + + if (!ch) + return; + + nd = ch->ch_nd; + + if (!nd) + return; + + spin_lock_irqsave(&nd->nd_lock, lock_flags); + + + /* Used to be on channel basis, now we check on a unit basis. */ + if (un->un_open_count != 1) + goto unlock; + + /* + * OK, its the last close on the unit + */ + un->un_flag |= UN_CLOSING; + + /* + * Notify the discipline to only process XON/XOFF characters. + */ + tty->closing = 1; + + /* + * Wait for output to drain only if this is + * the last close against the channel + */ + + if (ch->ch_open_count == 1) { + /* + * If its the print device, we need to ensure at all costs that + * the offstr will fit. If it won't, flush our tbuf. + */ + if (IS_PRINT(MINOR(tty_devnum(tty))) && + (((ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK) < + ch->ch_digi.digi_offlen)) + ch->ch_tin = ch->ch_tout; + + /* + * Turn off the printer. Don't bother checking to see if its + * IS_PRINT... Since this is the last close the flag is going + * to be cleared, so we MUST make sure the offstr gets inserted + * into tbuf. + */ + + if ((ch->ch_flag & CH_PRON) != 0) { + drp_wmove(ch, 0, ch->ch_digi.digi_offstr, + ch->ch_digi.digi_offlen); + ch->ch_flag &= ~CH_PRON; + sent_printer_offstr = 1; + } + } + + /* + * Wait until either the output queue has drained, or we see + * absolutely no progress for 15 seconds. + */ + + tpos = ch->ch_s_tpos; + + waketime = jiffies + 15 * HZ; + + for (;;) { + + /* + * Make sure the port still exists. + */ + + if (port >= nd->nd_chan_count) { + err = 1; + break; + } + + if (signal_pending(current)) { + err = 1; + break; + } + + /* + * If the port is idle (not opened on the server), we have + * no way of draining/flushing/closing the port on that server. + * So break out of loop. + */ + if (ch->ch_state == CS_IDLE) + break; + + nd->nd_tx_work = 1; + + /* + * Exit if the queues for this unit are empty, + * and either the other unit is still open or all + * data has drained. + */ + + if ((un->un_tty)->ops->chars_in_buffer ? + ((un->un_tty)->ops->chars_in_buffer)(un->un_tty) == 0 : 1) { + + /* + * We don't need to wait for a buffer to drain + * if the other unit is open. + */ + + if (ch->ch_open_count != un->un_open_count) + break; + + /* + * The wait is complete when all queues are + * drained, and any break in progress is complete. + */ + + if (ch->ch_tin == ch->ch_tout && + ch->ch_s_tin == ch->ch_s_tpos && + (ch->ch_send & RR_TX_BREAK) == 0) { + break; + } + } + + /* + * Flush TX data and exit the wait if NDELAY is set, + * or this is not a DIGI printer, and the close timeout + * expires. + */ + + if ((file->f_flags & (O_NDELAY | O_NONBLOCK)) || + ((long)(jiffies - waketime) >= 0 && + (ch->ch_digi.digi_flags & DIGI_PRINTER) == 0)) { + + /* + * If we sent the printer off string, we cannot + * flush our internal buffers, or we might lose + * the offstr. + */ + if (!sent_printer_offstr) + dgrp_tty_flush_buffer(tty); + + tty_ldisc_flush(tty); + break; + } + + /* + * Otherwise take a short nap. + */ + + ch->ch_flag |= CH_DRAIN; + + spin_unlock_irqrestore(&nd->nd_lock, lock_flags); + + schedule_timeout_interruptible(1); + s = signal_pending(current); + + spin_lock_irqsave(&nd->nd_lock, lock_flags); + + if (s) { + /* + * If we had sent the printer off string, we now have + * some problems. + * + * The system won't let us sleep since we got an error + * back from sleep, presumably because the user did + * a ctrl-c... + * But we need to ensure that the offstr gets sent! + * Thus, we have to do something else besides sleeping. + * The plan: + * 1) Make this task uninterruptable. + * 2) Set up a timer to go off in 1 sec. + * 3) Act as tho we just got out of the sleep above. + * + * Thankfully, in the real world, this just + * never happens. + */ + + if (sent_printer_offstr) { + spin_unlock_irqrestore(&nd->nd_lock, + lock_flags); + drp_my_sleep(ch); + spin_lock_irqsave(&nd->nd_lock, lock_flags); + } else { + err = 1; + break; + } + } + + /* + * Restart the wait if any progress is seen. + */ + + if (ch->ch_s_tpos != tpos) { + tpos = ch->ch_s_tpos; + + /* TODO: this gives us timeout problems with nist ?? */ + waketime = jiffies + 15 * HZ; + } + } + + /* + * Close the line discipline + */ + + /* this is done in tty_io.c */ + /* if ((un->un_tty)->ldisc.close) + * ((un->un_tty)->ldisc.close)(un->un_tty); + */ + + /* don't do this here */ + /* un->un_flag = 0; */ + + /* + * Flush the receive buffer on terminal unit close only. + */ + + if (!IS_PRINT(MINOR(tty_devnum(tty)))) + ch->ch_rout = ch->ch_rin; + + + /* + * Don't permit the close to happen until we get any pending + * sync request responses. + * There could be other ports depending upon the response as well. + * + * Also, don't permit the close to happen until any parameter + * changes have been sent out from the state machine as well. + * This is required because of a ditty -a race with -HUPCL + * We MUST make sure all channel parameters have been sent to the + * Portserver before sending a close. + */ + + if ((err != 1) && (ch->ch_state != CS_IDLE)) { + spin_unlock_irqrestore(&nd->nd_lock, lock_flags); + s = wait_event_interruptible(ch->ch_flag_wait, + ((ch->ch_flag & (CH_WAITING_SYNC | CH_PARAM)) == 0)); + spin_lock_irqsave(&nd->nd_lock, lock_flags); + } + + /* + * Cleanup the channel if last unit open. + */ + + if (ch->ch_open_count == 1) { + ch->ch_flag = 0; + ch->ch_category = 0; + ch->ch_send = 0; + ch->ch_expect = 0; + ch->ch_tout = ch->ch_tin; + /* (un->un_tty)->device = 0; */ + + if (ch->ch_state == CS_READY) + ch->ch_state = CS_SEND_CLOSE; + } + + /* + * Send the changes to the server + */ + if (ch->ch_state != CS_IDLE) { + ch->ch_flag |= CH_PARAM; + wake_up_interruptible(&ch->ch_flag_wait); + } + + nd->nd_tx_work = 1; + nd->nd_tx_ready = 1; + +unlock: + tty->closing = 0; + + if (ch->ch_open_count <= 0) + dev_info(tty->dev, + "%s - unexpected value for ch->ch_open_count: %i\n", + __func__, ch->ch_open_count); + else + ch->ch_open_count--; + + if (un->un_open_count <= 0) + dev_info(tty->dev, + "%s - unexpected value for un->un_open_count: %i\n", + __func__, un->un_open_count); + else + un->un_open_count--; + + un->un_flag &= ~(UN_NORMAL_ACTIVE | UN_CALLOUT_ACTIVE | UN_CLOSING); + if (waitqueue_active(&un->un_close_wait)) + wake_up_interruptible(&un->un_close_wait); + + spin_unlock_irqrestore(&nd->nd_lock, lock_flags); + + return; + +} + +static void drp_wmove(struct ch_struct *ch, int from_user, void *buf, int count) +{ + int n; + int ret = 0; + + ch->ch_nd->nd_tx_work = 1; + + n = TBUF_MAX - ch->ch_tin; + + if (count >= n) { + if (from_user) + ret = copy_from_user(ch->ch_tbuf + ch->ch_tin, + (void __user *) buf, n); + else + memcpy(ch->ch_tbuf + ch->ch_tin, buf, n); + + buf = (char *) buf + n; + count -= n; + ch->ch_tin = 0; + } + + if (from_user) + ret = copy_from_user(ch->ch_tbuf + ch->ch_tin, + (void __user *) buf, count); + else + memcpy(ch->ch_tbuf + ch->ch_tin, buf, count); + + ch->ch_tin += count; +} + + +static int dgrp_calculate_txprint_bounds(struct ch_struct *ch, int space, + int *un_flag) +{ + clock_t tt; + clock_t mt; + unsigned short tmax = 0; + + /* + * If the terminal device is busy, reschedule when + * the terminal device becomes idle. + */ + + if (ch->ch_tun.un_open_count != 0 && + ch->ch_tun.un_tty->ops->chars_in_buffer && + ((ch->ch_tun.un_tty->ops->chars_in_buffer)(ch->ch_tun.un_tty) != 0)) { + *un_flag = UN_PWAIT; + return 0; + } + + /* + * Assure that whenever there is printer data in the output + * buffer, there always remains enough space after it to + * turn the printer off. + */ + space -= ch->ch_digi.digi_offlen; + + if (space <= 0) { + *un_flag = UN_EMPTY; + return 0; + } + + /* + * We measure printer CPS speed by incrementing + * ch_cpstime by (HZ / digi_maxcps) for every + * character we output, restricting output so + * that ch_cpstime never exceeds lbolt. + * + * However if output has not been done for some + * time, lbolt will grow to very much larger than + * ch_cpstime, which would allow essentially + * unlimited amounts of output until ch_cpstime + * finally caught up. To avoid this, we adjust + * cps_time when necessary so the difference + * between lbolt and ch_cpstime never results + * in sending more than digi_bufsize characters. + * + * This nicely models a printer with an internal + * buffer of digi_bufsize characters. + * + * Get the time between lbolt and ch->ch_cpstime; + */ + + tt = jiffies - ch->ch_cpstime; + + /* + * Compute the time required to send digi_bufsize + * characters. + */ + + mt = HZ * ch->ch_digi.digi_bufsize / ch->ch_digi.digi_maxcps; + + /* + * Compute the number of characters that can be sent + * without violating the time constraint. If the + * direct calculation of this number is bigger than + * digi_bufsize, limit the number to digi_bufsize, + * and adjust cpstime to match. + */ + + if ((clock_t)(tt + HZ) > (clock_t)(mt + HZ)) { + tmax = ch->ch_digi.digi_bufsize; + ch->ch_cpstime = jiffies - mt; + } else { + tmax = ch->ch_digi.digi_maxcps * tt / HZ; + } + + /* + * If the time constraint now binds, limit the transmit + * count accordingly, and tentatively arrange to be + * rescheduled based on time. + */ + + if (tmax < space) { + *un_flag = UN_TIME; + space = tmax; + } + + /* + * Compute the total number of characters we can + * output before the total number of characters known + * to be in the output queue exceeds digi_maxchar. + */ + + tmax = (ch->ch_digi.digi_maxchar - + ((ch->ch_tin - ch->ch_tout) & TBUF_MASK) - + ((ch->ch_s_tin - ch->ch_s_tpos) & 0xffff)); + + + /* + * If the digi_maxchar constraint now holds, limit + * the transmit count accordingly, and arrange to + * be rescheduled when the queue becomes empty. + */ + + if (space > tmax) { + *un_flag = UN_EMPTY; + space = tmax; + } + + if (space <= 0) + *un_flag |= UN_EMPTY; + + return space; +} + + +static int dgrp_tty_write(struct tty_struct *tty, + const unsigned char *buf, + int count) +{ + struct nd_struct *nd; + struct un_struct *un; + struct ch_struct *ch; + int space; + int n; + int t; + int sendcount; + int un_flag; + ulong lock_flags; + + if (tty == NULL) + return 0; + + un = tty->driver_data; + if (!un) + return 0; + + ch = un->un_ch; + if (!ch) + return 0; + + nd = ch->ch_nd; + if (!nd) + return 0; + + /* + * Ignore the request if the channel is not ready. + */ + if (ch->ch_state != CS_READY) + return 0; + + spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags); + + /* + * Ignore the request if output is blocked. + */ + if ((un->un_flag & (UN_EMPTY | UN_LOW | UN_TIME | UN_PWAIT)) != 0) { + count = 0; + goto out; + } + + /* + * Also ignore the request if DPA has this port open, + * and is flow controlled on reading more data. + */ + if (nd->nd_dpa_debug && nd->nd_dpa_flag & DPA_WAIT_SPACE && + nd->nd_dpa_port == MINOR(tty_devnum(ch->ch_tun.un_tty))) { + count = 0; + goto out; + } + + /* + * Limit amount we will write to the amount of space + * available in the channel buffer. + */ + sendcount = 0; + + space = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK; + + /* + * Handle the printer device. + */ + + un_flag = UN_LOW; + + if (IS_PRINT(MINOR(tty_devnum(tty)))) { + clock_t tt; + clock_t mt; + unsigned short tmax = 0; + + /* + * If the terminal device is busy, reschedule when + * the terminal device becomes idle. + */ + + if (ch->ch_tun.un_open_count != 0 && + ((ch->ch_tun.un_tty->ops->chars_in_buffer)(ch->ch_tun.un_tty) != 0)) { + un->un_flag |= UN_PWAIT; + count = 0; + goto out; + } + + /* + * Assure that whenever there is printer data in the output + * buffer, there always remains enough space after it to + * turn the printer off. + */ + space -= ch->ch_digi.digi_offlen; + + /* + * Output the printer on string. + */ + + if ((ch->ch_flag & CH_PRON) == 0) { + space -= ch->ch_digi.digi_onlen; + + if (space < 0) { + un->un_flag |= UN_EMPTY; + (ch->ch_nd)->nd_tx_work = 1; + count = 0; + goto out; + } + + drp_wmove(ch, 0, ch->ch_digi.digi_onstr, + ch->ch_digi.digi_onlen); + + ch->ch_flag |= CH_PRON; + } + + /* + * We measure printer CPS speed by incrementing + * ch_cpstime by (HZ / digi_maxcps) for every + * character we output, restricting output so + * that ch_cpstime never exceeds lbolt. + * + * However if output has not been done for some + * time, lbolt will grow to very much larger than + * ch_cpstime, which would allow essentially + * unlimited amounts of output until ch_cpstime + * finally caught up. To avoid this, we adjust + * cps_time when necessary so the difference + * between lbolt and ch_cpstime never results + * in sending more than digi_bufsize characters. + * + * This nicely models a printer with an internal + * buffer of digi_bufsize characters. + * + * Get the time between lbolt and ch->ch_cpstime; + */ + + tt = jiffies - ch->ch_cpstime; + + /* + * Compute the time required to send digi_bufsize + * characters. + */ + + mt = HZ * ch->ch_digi.digi_bufsize / ch->ch_digi.digi_maxcps; + + /* + * Compute the number of characters that can be sent + * without violating the time constraint. If the + * direct calculation of this number is bigger than + * digi_bufsize, limit the number to digi_bufsize, + * and adjust cpstime to match. + */ + + if ((clock_t)(tt + HZ) > (clock_t)(mt + HZ)) { + tmax = ch->ch_digi.digi_bufsize; + ch->ch_cpstime = jiffies - mt; + } else { + tmax = ch->ch_digi.digi_maxcps * tt / HZ; + } + + /* + * If the time constraint now binds, limit the transmit + * count accordingly, and tentatively arrange to be + * rescheduled based on time. + */ + + if (tmax < space) { + space = tmax; + un_flag = UN_TIME; + } + + /* + * Compute the total number of characters we can + * output before the total number of characters known + * to be in the output queue exceeds digi_maxchar. + */ + + tmax = (ch->ch_digi.digi_maxchar - + ((ch->ch_tin - ch->ch_tout) & TBUF_MASK) - + ((ch->ch_s_tin - ch->ch_s_tpos) & 0xffff)); + + + /* + * If the digi_maxchar constraint now holds, limit + * the transmit count accordingly, and arrange to + * be rescheduled when the queue becomes empty. + */ + + if (space > tmax) { + space = tmax; + un_flag = UN_EMPTY; + } + + } + /* + * Handle the terminal device. + */ + else { + + /* + * If the printer device is on, turn it off. + */ + + if ((ch->ch_flag & CH_PRON) != 0) { + + space -= ch->ch_digi.digi_offlen; + + drp_wmove(ch, 0, ch->ch_digi.digi_offstr, + ch->ch_digi.digi_offlen); + + ch->ch_flag &= ~CH_PRON; + } + } + + /* + * If space is 0 and its because the ch->tbuf + * is full, then Linux will handle a callback when queue + * space becomes available. + * tty_write returns count = 0 + */ + + if (space <= 0) { + /* the linux tty_io.c handles this if we return 0 */ + /* if (fp->flags & O_NONBLOCK) return -EAGAIN; */ + + un->un_flag |= UN_EMPTY; + (ch->ch_nd)->nd_tx_work = 1; + count = 0; + goto out; + } + + count = min(count, space); + + if (count > 0) { + + un->un_tbusy++; + + /* + * Copy the buffer contents to the ch_tbuf + * being careful to wrap around the circular queue + */ + + t = TBUF_MAX - ch->ch_tin; + n = count; + + if (n >= t) { + memcpy(ch->ch_tbuf + ch->ch_tin, buf, t); + if (nd->nd_dpa_debug && nd->nd_dpa_port == PORT_NUM(MINOR(tty_devnum(un->un_tty)))) + dgrp_dpa_data(nd, 0, (char *) buf, t); + buf += t; + n -= t; + ch->ch_tin = 0; + sendcount += n; + } + + memcpy(ch->ch_tbuf + ch->ch_tin, buf, n); + if (nd->nd_dpa_debug && nd->nd_dpa_port == PORT_NUM(MINOR(tty_devnum(un->un_tty)))) + dgrp_dpa_data(nd, 0, (char *) buf, n); + buf += n; + ch->ch_tin += n; + sendcount += n; + + un->un_tbusy--; + (ch->ch_nd)->nd_tx_work = 1; + if (ch->ch_edelay != DGRP_RTIME) { + (ch->ch_nd)->nd_tx_ready = 1; + wake_up_interruptible(&nd->nd_tx_waitq); + } + } + + ch->ch_txcount += count; + + if (IS_PRINT(MINOR(tty_devnum(tty)))) { + + /* + * Adjust ch_cpstime to account + * for the characters just output. + */ + + if (sendcount > 0) { + int cc = HZ * sendcount + ch->ch_cpsrem; + + ch->ch_cpstime += cc / ch->ch_digi.digi_maxcps; + ch->ch_cpsrem = cc % ch->ch_digi.digi_maxcps; + } + + /* + * If we are now waiting on time, schedule ourself + * back when we'll be able to send a block of + * digi_maxchar characters. + */ + + if ((un_flag & UN_TIME) != 0) { + ch->ch_waketime = (ch->ch_cpstime + + (ch->ch_digi.digi_maxchar * HZ / + ch->ch_digi.digi_maxcps)); + } + } + + /* + * If the printer unit is waiting for completion + * of terminal output, get him going again. + */ + + if ((ch->ch_pun.un_flag & UN_PWAIT) != 0) + (ch->ch_nd)->nd_tx_work = 1; + +out: + spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags); + + return count; +} + + +/* + * Put a character into ch->ch_buf + * + * - used by the line discipline for OPOST processing + */ + +static int dgrp_tty_put_char(struct tty_struct *tty, unsigned char new_char) +{ + struct un_struct *un; + struct ch_struct *ch; + ulong lock_flags; + int space; + int retval = 0; + + if (tty == NULL) + return 0; + + un = tty->driver_data; + if (!un) + return 0; + + ch = un->un_ch; + if (!ch) + return 0; + + if (ch->ch_state != CS_READY) + return 0; + + spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags); + + + /* + * If space is 0 and its because the ch->tbuf + * Warn and dump the character, there isn't anything else + * we can do about it. David_Fries@digi.com + */ + + space = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK; + + un->un_tbusy++; + + /* + * Output the printer on string if device is TXPrint. + */ + if (IS_PRINT(MINOR(tty_devnum(tty))) && (ch->ch_flag & CH_PRON) == 0) { + if (space < ch->ch_digi.digi_onlen) { + un->un_tbusy--; + goto out; + } + space -= ch->ch_digi.digi_onlen; + drp_wmove(ch, 0, ch->ch_digi.digi_onstr, + ch->ch_digi.digi_onlen); + ch->ch_flag |= CH_PRON; + } + + /* + * Output the printer off string if device is NOT TXPrint. + */ + + if (!IS_PRINT(MINOR(tty_devnum(tty))) && + ((ch->ch_flag & CH_PRON) != 0)) { + if (space < ch->ch_digi.digi_offlen) { + un->un_tbusy--; + goto out; + } + + space -= ch->ch_digi.digi_offlen; + drp_wmove(ch, 0, ch->ch_digi.digi_offstr, + ch->ch_digi.digi_offlen); + ch->ch_flag &= ~CH_PRON; + } + + if (!space) { + un->un_tbusy--; + goto out; + } + + /* + * Copy the character to the ch_tbuf being + * careful to wrap around the circular queue + */ + ch->ch_tbuf[ch->ch_tin] = new_char; + ch->ch_tin = (1 + ch->ch_tin) & TBUF_MASK; + + if (IS_PRINT(MINOR(tty_devnum(tty)))) { + + /* + * Adjust ch_cpstime to account + * for the character just output. + */ + + int cc = HZ + ch->ch_cpsrem; + + ch->ch_cpstime += cc / ch->ch_digi.digi_maxcps; + ch->ch_cpsrem = cc % ch->ch_digi.digi_maxcps; + + /* + * If we are now waiting on time, schedule ourself + * back when we'll be able to send a block of + * digi_maxchar characters. + */ + + ch->ch_waketime = (ch->ch_cpstime + + (ch->ch_digi.digi_maxchar * HZ / + ch->ch_digi.digi_maxcps)); + } + + + un->un_tbusy--; + (ch->ch_nd)->nd_tx_work = 1; + + retval = 1; +out: + spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags); + return retval; +} + + + +/* + * Flush TX buffer (make in == out) + * + * check tty_ioctl.c -- this is called after TCOFLUSH + */ +static void dgrp_tty_flush_buffer(struct tty_struct *tty) +{ + struct un_struct *un; + struct ch_struct *ch; + + if (!tty) + return; + un = tty->driver_data; + if (!un) + return; + + ch = un->un_ch; + if (!ch) + return; + + ch->ch_tout = ch->ch_tin; + /* do NOT do this here! */ + /* ch->ch_s_tpos = ch->ch_s_tin = 0; */ + + /* send the flush output command now */ + ch->ch_send |= RR_TX_FLUSH; + (ch->ch_nd)->nd_tx_ready = 1; + (ch->ch_nd)->nd_tx_work = 1; + wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq); + + if (waitqueue_active(&tty->write_wait)) + wake_up_interruptible(&tty->write_wait); + + tty_wakeup(tty); + +} + +/* + * Return space available in Tx buffer + * count = ( ch->ch_tout - ch->ch_tin ) mod (TBUF_MAX - 1) + */ +static int dgrp_tty_write_room(struct tty_struct *tty) +{ + struct un_struct *un; + struct ch_struct *ch; + int count; + + if (!tty) + return 0; + + un = tty->driver_data; + if (!un) + return 0; + + ch = un->un_ch; + if (!ch) + return 0; + + count = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK; + + /* We *MUST* check this, and return 0 if the Printer Unit cannot + * take any more data within its time constraints... If we don't + * return 0 and the printer has hit it time constraint, the ld will + * call us back doing a put_char, which cannot be rejected!!! + */ + if (IS_PRINT(MINOR(tty_devnum(tty)))) { + int un_flag = 0; + count = dgrp_calculate_txprint_bounds(ch, count, &un_flag); + if (count <= 0) + count = 0; + + ch->ch_pun.un_flag |= un_flag; + (ch->ch_nd)->nd_tx_work = 1; + } + + return count; +} + +/* + * Return number of characters that have not been transmitted yet. + * chars_in_buffer = ( ch->ch_tin - ch->ch_tout ) mod (TBUF_MAX - 1) + * + ( ch->ch_s_tin - ch->ch_s_tout ) mod (0xffff) + * = number of characters "in transit" + * + * Remember that sequence number math is always with a sixteen bit + * mask, not the TBUF_MASK. + */ + +static int dgrp_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct un_struct *un; + struct ch_struct *ch; + int count; + int count1; + + if (!tty) + return 0; + + un = tty->driver_data; + if (!un) + return 0; + + ch = un->un_ch; + if (!ch) + return 0; + + count1 = count = (ch->ch_tin - ch->ch_tout) & TBUF_MASK; + count += (ch->ch_s_tin - ch->ch_s_tpos) & 0xffff; + /* one for tbuf, one for the PS */ + + /* + * If we are busy transmitting add 1 + */ + count += un->un_tbusy; + + return count; +} + + +/***************************************************************************** + * + * Helper applications for dgrp_tty_ioctl() + * + ***************************************************************************** + */ + + +/** + * ch_to_tty_flags() -- convert channel flags to termio flags + * @ch_flag: Digi channel flags + * @flagtype: type of ch_flag (iflag, oflag or cflag) + * + * take the channel flags of the specified type and return the + * corresponding termio flag + */ +static tcflag_t ch_to_tty_flags(ushort ch_flag, char flagtype) +{ + tcflag_t retval = 0; + + switch (flagtype) { + case 'i': + retval = ((ch_flag & IF_IGNBRK) ? IGNBRK : 0) + | ((ch_flag & IF_BRKINT) ? BRKINT : 0) + | ((ch_flag & IF_IGNPAR) ? IGNPAR : 0) + | ((ch_flag & IF_PARMRK) ? PARMRK : 0) + | ((ch_flag & IF_INPCK) ? INPCK : 0) + | ((ch_flag & IF_ISTRIP) ? ISTRIP : 0) + | ((ch_flag & IF_IXON) ? IXON : 0) + | ((ch_flag & IF_IXANY) ? IXANY : 0) + | ((ch_flag & IF_IXOFF) ? IXOFF : 0); + break; + + case 'o': + retval = ((ch_flag & OF_OLCUC) ? OLCUC : 0) + | ((ch_flag & OF_ONLCR) ? ONLCR : 0) + | ((ch_flag & OF_OCRNL) ? OCRNL : 0) + | ((ch_flag & OF_ONOCR) ? ONOCR : 0) + | ((ch_flag & OF_ONLRET) ? ONLRET : 0) + /* | ((ch_flag & OF_OTAB3) ? OFILL : 0) */ + | ((ch_flag & OF_TABDLY) ? TABDLY : 0); + break; + + case 'c': + retval = ((ch_flag & CF_CSTOPB) ? CSTOPB : 0) + | ((ch_flag & CF_CREAD) ? CREAD : 0) + | ((ch_flag & CF_PARENB) ? PARENB : 0) + | ((ch_flag & CF_PARODD) ? PARODD : 0) + | ((ch_flag & CF_HUPCL) ? HUPCL : 0); + + switch (ch_flag & CF_CSIZE) { + case CF_CS5: + retval |= CS5; + break; + case CF_CS6: + retval |= CS6; + break; + case CF_CS7: + retval |= CS7; + break; + case CF_CS8: + retval |= CS8; + break; + default: + retval |= CS8; + break; + } + break; + case 'x': + break; + case 'l': + break; + default: + return 0; + } + + return retval; +} + + +/** + * tty_to_ch_flags() -- convert termio flags to digi channel flags + * @tty: pointer to a TTY structure holding flag to be converted + * @flagtype: identifies which flag (iflags, oflags, or cflags) should + * be converted + * + * take the termio flag of the specified type and return the + * corresponding Digi version of the flags + */ +static ushort tty_to_ch_flags(struct tty_struct *tty, char flagtype) +{ + ushort retval = 0; + tcflag_t tflag = 0; + + switch (flagtype) { + case 'i': + tflag = tty->termios.c_iflag; + retval = (I_IGNBRK(tty) ? IF_IGNBRK : 0) + | (I_BRKINT(tty) ? IF_BRKINT : 0) + | (I_IGNPAR(tty) ? IF_IGNPAR : 0) + | (I_PARMRK(tty) ? IF_PARMRK : 0) + | (I_INPCK(tty) ? IF_INPCK : 0) + | (I_ISTRIP(tty) ? IF_ISTRIP : 0) + | (I_IXON(tty) ? IF_IXON : 0) + | (I_IXANY(tty) ? IF_IXANY : 0) + | (I_IXOFF(tty) ? IF_IXOFF : 0); + break; + case 'o': + tflag = tty->termios.c_oflag; + /* + * If OPOST is set, then do the post processing in the + * firmware by setting all the processing flags on. + * If ~OPOST, then make sure we are not doing any + * output processing!! + */ + if (!O_OPOST(tty)) + retval = 0; + else + retval = (O_OLCUC(tty) ? OF_OLCUC : 0) + | (O_ONLCR(tty) ? OF_ONLCR : 0) + | (O_OCRNL(tty) ? OF_OCRNL : 0) + | (O_ONOCR(tty) ? OF_ONOCR : 0) + | (O_ONLRET(tty) ? OF_ONLRET : 0) + /* | (O_OFILL(tty) ? OF_TAB3 : 0) */ + | (O_TABDLY(tty) ? OF_TABDLY : 0); + break; + case 'c': + tflag = tty->termios.c_cflag; + retval = (C_CSTOPB(tty) ? CF_CSTOPB : 0) + | (C_CREAD(tty) ? CF_CREAD : 0) + | (C_PARENB(tty) ? CF_PARENB : 0) + | (C_PARODD(tty) ? CF_PARODD : 0) + | (C_HUPCL(tty) ? CF_HUPCL : 0); + switch (C_CSIZE(tty)) { + case CS8: + retval |= CF_CS8; + break; + case CS7: + retval |= CF_CS7; + break; + case CS6: + retval |= CF_CS6; + break; + case CS5: + retval |= CF_CS5; + break; + default: + retval |= CF_CS8; + break; + } + break; + case 'x': + break; + case 'l': + break; + default: + return 0; + } + + return retval; +} + + +static int dgrp_tty_send_break(struct tty_struct *tty, int msec) +{ + struct un_struct *un; + struct ch_struct *ch; + int ret = -EIO; + + if (!tty) + return ret; + + un = tty->driver_data; + if (!un) + return ret; + + ch = un->un_ch; + if (!ch) + return ret; + + dgrp_send_break(ch, msec); + return 0; +} + + +/* + * This routine sends a break character out the serial port. + * + * duration is in 1/1000's of a second + */ +static int dgrp_send_break(struct ch_struct *ch, int msec) +{ + ulong x; + + wait_event_interruptible(ch->ch_flag_wait, + ((ch->ch_flag & CH_TX_BREAK) == 0)); + ch->ch_break_time += max(msec, 250); + ch->ch_send |= RR_TX_BREAK; + ch->ch_flag |= CH_TX_BREAK; + (ch->ch_nd)->nd_tx_work = 1; + + x = (msec * HZ) / 1000; + wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq); + + return 0; +} + + +/* + * Return modem signals to ld. + */ +static int dgrp_tty_tiocmget(struct tty_struct *tty) +{ + unsigned int mlast; + struct un_struct *un = tty->driver_data; + struct ch_struct *ch; + + if (!un) + return -ENODEV; + + ch = un->un_ch; + if (!ch) + return -ENODEV; + + mlast = ((ch->ch_s_mlast & ~(DM_RTS | DM_DTR)) | + (ch->ch_mout & (DM_RTS | DM_DTR))); + + /* defined in /usr/include/asm/termios.h */ + mlast = ((mlast & DM_RTS) ? TIOCM_RTS : 0) + | ((mlast & DM_DTR) ? TIOCM_DTR : 0) + | ((mlast & DM_CD) ? TIOCM_CAR : 0) + | ((mlast & DM_RI) ? TIOCM_RNG : 0) + | ((mlast & DM_DSR) ? TIOCM_DSR : 0) + | ((mlast & DM_CTS) ? TIOCM_CTS : 0); + + return mlast; +} + + +/* + * Set modem lines + */ +static int dgrp_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + ulong lock_flags; + struct un_struct *un = tty->driver_data; + struct ch_struct *ch; + + if (!un) + return -ENODEV; + + ch = un->un_ch; + if (!ch) + return -ENODEV; + + if (set & TIOCM_RTS) + ch->ch_mout |= DM_RTS; + + if (set & TIOCM_DTR) + ch->ch_mout |= DM_DTR; + + if (clear & TIOCM_RTS) + ch->ch_mout &= ~(DM_RTS); + + if (clear & TIOCM_DTR) + ch->ch_mout &= ~(DM_DTR); + + spin_lock_irqsave(&(ch->ch_nd)->nd_lock, lock_flags); + ch->ch_flag |= CH_PARAM; + (ch->ch_nd)->nd_tx_work = 1; + wake_up_interruptible(&ch->ch_flag_wait); + + spin_unlock_irqrestore(&(ch->ch_nd)->nd_lock, lock_flags); + + return 0; +} + + + +/* + * Get current modem status + */ +static int get_modem_info(struct ch_struct *ch, unsigned int *value) +{ + unsigned int mlast; + + mlast = ((ch->ch_s_mlast & ~(DM_RTS | DM_DTR)) | + (ch->ch_mout & (DM_RTS | DM_DTR))); + + /* defined in /usr/include/asm/termios.h */ + mlast = ((mlast & DM_RTS) ? TIOCM_RTS : 0) + | ((mlast & DM_DTR) ? TIOCM_DTR : 0) + | ((mlast & DM_CD) ? TIOCM_CAR : 0) + | ((mlast & DM_RI) ? TIOCM_RNG : 0) + | ((mlast & DM_DSR) ? TIOCM_DSR : 0) + | ((mlast & DM_CTS) ? TIOCM_CTS : 0); + put_user(mlast, (unsigned int __user *) value); + + return 0; +} + +/* + * Set modem lines + */ +static int set_modem_info(struct ch_struct *ch, unsigned int command, + unsigned int *value) +{ + int error; + unsigned int arg; + int mval = 0; + ulong lock_flags; + + error = access_ok(VERIFY_READ, (void __user *) value, sizeof(int)); + if (error == 0) + return -EFAULT; + + get_user(arg, (unsigned int __user *) value); + mval |= ((arg & TIOCM_RTS) ? DM_RTS : 0) + | ((arg & TIOCM_DTR) ? DM_DTR : 0); + + switch (command) { + case TIOCMBIS: /* set flags */ + ch->ch_mout |= mval; + break; + case TIOCMBIC: /* clear flags */ + ch->ch_mout &= ~mval; + break; + case TIOCMSET: + ch->ch_mout = mval; + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&(ch->ch_nd)->nd_lock, lock_flags); + + ch->ch_flag |= CH_PARAM; + (ch->ch_nd)->nd_tx_work = 1; + wake_up_interruptible(&ch->ch_flag_wait); + + spin_unlock_irqrestore(&(ch->ch_nd)->nd_lock, lock_flags); + + return 0; +} + + +/* + * Assign the custom baud rate to the channel structure + */ +static void dgrp_set_custom_speed(struct ch_struct *ch, int newrate) +{ + int testdiv; + int testrate_high; + int testrate_low; + + int deltahigh, deltalow; + + if (newrate < 0) + newrate = 0; + + /* + * 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 < ((PORTSERVER_DIVIDEND / 0xFFFF) + 1)) + newrate = ((PORTSERVER_DIVIDEND / 0xFFFF) + 1); + if (newrate && newrate > PORTSERVER_DIVIDEND) + newrate = PORTSERVER_DIVIDEND; + + while (newrate > 0) { + testdiv = PORTSERVER_DIVIDEND / newrate; + + /* + * If we try to figure out what rate the PortServer 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 = PORTSERVER_DIVIDEND / testdiv; + testrate_low = PORTSERVER_DIVIDEND / (testdiv + 1); + + /* + * If the rate for the requested divisor is correct, just + * use it and be done. + */ + if (testrate_high == newrate) + break; + + /* + * 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; + + break; + } + + ch->ch_custom_speed = newrate; + + drp_param(ch); + + return; +} + + +/* + # dgrp_tty_digiseta() + * + * Ioctl to set the information from ditty. + * + * NOTE: DIGI_IXON, DSRPACE, DCDPACE, and DTRPACE are unsupported. JAR 990922 + */ +static int dgrp_tty_digiseta(struct tty_struct *tty, + struct digi_struct *new_info) +{ + struct un_struct *un = tty->driver_data; + struct ch_struct *ch; + + if (!un) + return -ENODEV; + + ch = un->un_ch; + if (!ch) + return -ENODEV; + + if (copy_from_user(&ch->ch_digi, (void __user *) new_info, + sizeof(struct digi_struct))) + return -EFAULT; + + if ((ch->ch_digi.digi_flags & RTSPACE) || + (ch->ch_digi.digi_flags & CTSPACE)) + tty->termios.c_cflag |= CRTSCTS; + else + tty->termios.c_cflag &= ~CRTSCTS; + + 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; + + /* make the changes now */ + drp_param(ch); + + return 0; +} + + + +/* + * dgrp_tty_digigetedelay() + * + * Ioctl to get the current edelay setting. + * + * + * + */ +static int dgrp_tty_digigetedelay(struct tty_struct *tty, int *retinfo) +{ + struct un_struct *un; + struct ch_struct *ch; + int tmp; + + if (!retinfo) + return -EFAULT; + + if (!tty || tty->magic != TTY_MAGIC) + return -EFAULT; + + un = tty->driver_data; + + if (!un) + return -ENODEV; + + ch = un->un_ch; + if (!ch) + return -ENODEV; + + tmp = ch->ch_edelay; + + if (copy_to_user((void __user *) retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + + return 0; +} + + +/* + * dgrp_tty_digisetedelay() + * + * Ioctl to set the EDELAY setting + * + */ +static int dgrp_tty_digisetedelay(struct tty_struct *tty, int *new_info) +{ + struct un_struct *un; + struct ch_struct *ch; + int new_digi; + + if (!tty || tty->magic != TTY_MAGIC) + return -EFAULT; + + un = tty->driver_data; + + if (!un) + return -ENODEV; + + ch = un->un_ch; + if (!ch) + return -ENODEV; + + if (copy_from_user(&new_digi, (void __user *)new_info, sizeof(int))) + return -EFAULT; + + ch->ch_edelay = new_digi; + + /* make the changes now */ + drp_param(ch); + + return 0; +} + + +/* + * The usual assortment of ioctl's + * + * note: use tty_check_change to make sure that we are not + * changing the state of a terminal when we are not a process + * in the forground. See tty_io.c + * rc = tty_check_change(tty); + * if (rc) return rc; + */ +static int dgrp_tty_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct un_struct *un; + struct ch_struct *ch; + int rc; + struct digiflow_struct dflow; + + if (!tty) + return -ENODEV; + + un = tty->driver_data; + if (!un) + return -ENODEV; + + ch = un->un_ch; + if (!ch) + return -ENODEV; + + 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 + */ + + rc = tty_check_change(tty); + if (rc) + return rc; + tty_wait_until_sent(tty, 0); + + if (!arg) + rc = dgrp_send_break(ch, 250); /* 1/4 second */ + + if (dgrp_tty_chars_in_buffer(tty) != 0) + return -EINTR; + + 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); + if (rc) + return rc; + tty_wait_until_sent(tty, 0); + + rc = dgrp_send_break(ch, arg ? arg*250 : 250); + + if (dgrp_tty_chars_in_buffer(tty) != 0) + return -EINTR; + return 0; + + case TIOCSBRK: + rc = tty_check_change(tty); + if (rc) + return rc; + tty_wait_until_sent(tty, 0); + + /* + * RealPort doesn't support turning on a break unconditionally. + * The RealPort device will stop sending a break automatically + * after the specified time value that we send in. + */ + rc = dgrp_send_break(ch, 250); /* 1/4 second */ + + if (dgrp_tty_chars_in_buffer(tty) != 0) + return -EINTR; + return 0; + + case TIOCCBRK: + /* + * RealPort doesn't support turning off a break unconditionally. + * The RealPort device will stop sending a break automatically + * after the specified time value that was sent when turning on + * the break. + */ + return 0; + + case TIOCGSOFTCAR: + rc = access_ok(VERIFY_WRITE, (void __user *) arg, + sizeof(long)); + if (rc == 0) + return -EFAULT; + put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg); + return 0; + + case TIOCSSOFTCAR: + get_user(arg, (unsigned long __user *) arg); + tty->termios.c_cflag = + ((tty->termios.c_cflag & ~CLOCAL) | + (arg ? CLOCAL : 0)); + return 0; + + case TIOCMGET: + rc = access_ok(VERIFY_WRITE, (void __user *) arg, + sizeof(unsigned int)); + if (rc == 0) + return -EFAULT; + return get_modem_info(ch, (unsigned int *) arg); + + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + return set_modem_info(ch, cmd, (unsigned int *) arg); + + /* + * 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) + return rc; + + switch (arg) { + case TCIFLUSH: + case TCIOFLUSH: + /* only flush input if this is the only open unit */ + if (!IS_PRINT(MINOR(tty_devnum(tty)))) { + ch->ch_rout = ch->ch_rin; + ch->ch_send |= RR_RX_FLUSH; + (ch->ch_nd)->nd_tx_work = 1; + (ch->ch_nd)->nd_tx_ready = 1; + wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq); + } + if (arg == TCIFLUSH) + break; + + case TCOFLUSH: /* flush output, or the receive buffer */ + /* + * This is handled in the tty_ioctl.c code + * calling tty_flush_buffer + */ + break; + + default: + /* POSIX.1 says return EINVAL if we got a bad arg */ + return -EINVAL; + } + /* pretend we didn't recognize this IOCTL */ + return -ENOIOCTLCMD; + +#ifdef TIOCGETP + case TIOCGETP: +#endif + /***************************************** + Linux HPUX Function + TCSETA TCSETA - set the termios + TCSETAF TCSETAF - wait for drain first, then set termios + TCSETAW TCSETAW - wait for drain, flush the input queue, then set termios + - looking at the tty_ioctl code, these command all call our + tty_set_termios at the driver's end, when a TCSETA* is sent, + it is expecting the tty to have a termio structure, + NOT a termios stucture. These two structures differ in size + and the tty_ioctl code does a conversion before processing them both. + - we should treat the TCSETAW TCSETAF ioctls the same, and let + the tty_ioctl code do the conversion stuff. + + TCSETS + TCSETSF (none) + TCSETSW + - the associated tty structure has a termios structure. + *****************************************/ + + case TCGETS: + case TCGETA: + return -ENOIOCTLCMD; + + case TCSETAW: + case TCSETAF: + 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. + */ + + /* + * Also, now that we have TXPrint, we have to check + * if this is the TXPrint device and the terminal + * device is open. If so, do NOT run check_change, + * as the terminal device is ALWAYS the parent. + */ + if (!IS_PRINT(MINOR(tty_devnum(tty))) || + !ch->ch_tun.un_open_count) { + rc = tty_check_change(tty); + if (rc) + return rc; + } + + /* wait for all the characters in tbuf to drain */ + tty_wait_until_sent(tty, 0); + + if ((cmd == TCSETSF) || (cmd == TCSETAF)) { + /* flush the contents of the rbuf queue */ + /* TODO: check if this is print device? */ + ch->ch_send |= RR_RX_FLUSH; + (ch->ch_nd)->nd_tx_ready = 1; + (ch->ch_nd)->nd_tx_work = 1; + wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq); + /* do we need to do this? just to be safe! */ + ch->ch_rout = ch->ch_rin; + } + + /* pretend we didn't recognize this */ + return -ENOIOCTLCMD; + + case TCXONC: + /* + * The Linux Line Discipline (LD) would do this for us if we + * let it, but we have the special firmware options to do this + * the "right way" regardless of hardware or software flow + * control so we'll do it outselves instead of letting the LD + * do it. + */ + rc = tty_check_change(tty); + if (rc) + return rc; + + switch (arg) { + case TCOON: + dgrp_tty_start(tty); + return 0; + case TCOOFF: + dgrp_tty_stop(tty); + return 0; + case TCION: + dgrp_tty_input_start(tty); + return 0; + case TCIOFF: + dgrp_tty_input_stop(tty); + return 0; + default: + return -EINVAL; + } + + case DIGI_GETA: + /* get information for ditty */ + if (copy_to_user((struct digi_struct __user *) arg, + &ch->ch_digi, sizeof(struct digi_struct))) + return -EFAULT; + break; + + case DIGI_SETAW: + case DIGI_SETAF: + /* wait for all the characters in tbuf to drain */ + tty_wait_until_sent(tty, 0); + + if (cmd == DIGI_SETAF) { + /* flush the contents of the rbuf queue */ + /* send down a packet with RR_RX_FLUSH set */ + ch->ch_send |= RR_RX_FLUSH; + (ch->ch_nd)->nd_tx_ready = 1; + (ch->ch_nd)->nd_tx_work = 1; + wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq); + /* do we need to do this? just to be safe! */ + ch->ch_rout = ch->ch_rin; + } + + /* pretend we didn't recognize this */ + + case DIGI_SETA: + return dgrp_tty_digiseta(tty, (struct digi_struct *) arg); + + case DIGI_SEDELAY: + return dgrp_tty_digisetedelay(tty, (int *) arg); + + case DIGI_GEDELAY: + return dgrp_tty_digigetedelay(tty, (int *) arg); + + case DIGI_GETFLOW: + case DIGI_GETAFLOW: + if (cmd == (DIGI_GETFLOW)) { + dflow.startc = tty->termios.c_cc[VSTART]; + dflow.stopc = tty->termios.c_cc[VSTOP]; + } else { + dflow.startc = ch->ch_xxon; + dflow.stopc = ch->ch_xxoff; + } + + if (copy_to_user((char __user *)arg, &dflow, sizeof(dflow))) + return -EFAULT; + break; + + case DIGI_SETFLOW: + case DIGI_SETAFLOW: + + if (copy_from_user(&dflow, (char __user *)arg, sizeof(dflow))) + return -EFAULT; + + if (cmd == (DIGI_SETFLOW)) { + tty->termios.c_cc[VSTART] = dflow.startc; + tty->termios.c_cc[VSTOP] = dflow.stopc; + } else { + ch->ch_xxon = dflow.startc; + ch->ch_xxoff = dflow.stopc; + } + break; + + case DIGI_GETCUSTOMBAUD: + rc = access_ok(VERIFY_WRITE, (void __user *) arg, sizeof(int)); + if (rc == 0) + return -EFAULT; + put_user(ch->ch_custom_speed, (unsigned int __user *) arg); + break; + + case DIGI_SETCUSTOMBAUD: + { + int new_rate; + + get_user(new_rate, (unsigned int __user *) arg); + dgrp_set_custom_speed(ch, new_rate); + + break; + } + + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +/* + * This routine allows the tty driver to be notified when + * the device's termios setting have changed. Note that we + * should be prepared to accept the case where old == NULL + * and try to do something rational. + * + * So we need to make sure that our copies of ch_oflag, + * ch_clag, and ch_iflag reflect the tty->termios flags. + */ +static void dgrp_tty_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + struct ktermios *ts; + struct ch_struct *ch; + struct un_struct *un; + + /* seems silly, but we have to check all these! */ + if (!tty) + return; + + un = tty->driver_data; + if (!un) + return; + + ts = &tty->termios; + + ch = un->un_ch; + if (!ch) + return; + + drp_param(ch); + + /* the CLOCAL flag has just been set */ + if (!(old->c_cflag & CLOCAL) && C_CLOCAL(tty)) + wake_up_interruptible(&un->un_open_wait); +} + + +/* + * Throttle receiving data. We just set a bit and stop reading + * data out of the channel buffer. It will back up and the + * FEP will do whatever is necessary to stop the far end. + */ +static void dgrp_tty_throttle(struct tty_struct *tty) +{ + struct ch_struct *ch; + + if (!tty) + return; + + ch = ((struct un_struct *) tty->driver_data)->un_ch; + if (!ch) + return; + + ch->ch_flag |= CH_RXSTOP; +} + + +static void dgrp_tty_unthrottle(struct tty_struct *tty) +{ + struct ch_struct *ch; + + if (!tty) + return; + + ch = ((struct un_struct *) tty->driver_data)->un_ch; + if (!ch) + return; + + ch->ch_flag &= ~CH_RXSTOP; +} + +/* + * Stop the transmitter + */ +static void dgrp_tty_stop(struct tty_struct *tty) +{ + struct ch_struct *ch; + + if (!tty) + return; + + ch = ((struct un_struct *) tty->driver_data)->un_ch; + if (!ch) + return; + + ch->ch_send |= RR_TX_STOP; + ch->ch_send &= ~RR_TX_START; + + /* make the change NOW! */ + (ch->ch_nd)->nd_tx_ready = 1; + if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq)) + wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq); +} + +/* + * Start the transmitter + */ +static void dgrp_tty_start(struct tty_struct *tty) +{ + struct ch_struct *ch; + + if (!tty) + return; + + ch = ((struct un_struct *) tty->driver_data)->un_ch; + if (!ch) + return; + + /* TODO: don't do anything if the transmitter is not stopped */ + + ch->ch_send |= RR_TX_START; + ch->ch_send &= ~RR_TX_STOP; + + /* make the change NOW! */ + (ch->ch_nd)->nd_tx_ready = 1; + (ch->ch_nd)->nd_tx_work = 1; + if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq)) + wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq); + +} + +/* + * Stop the reciever + */ +static void dgrp_tty_input_stop(struct tty_struct *tty) +{ + struct ch_struct *ch; + + if (!tty) + return; + + ch = ((struct un_struct *) tty->driver_data)->un_ch; + if (!ch) + return; + + ch->ch_send |= RR_RX_STOP; + ch->ch_send &= ~RR_RX_START; + (ch->ch_nd)->nd_tx_ready = 1; + if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq)) + wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq); + +} + + +static void dgrp_tty_send_xchar(struct tty_struct *tty, char c) +{ + struct un_struct *un; + struct ch_struct *ch; + + if (!tty) + return; + + un = tty->driver_data; + if (!un) + return; + + ch = un->un_ch; + if (!ch) + return; + if (c == STOP_CHAR(tty)) + ch->ch_send |= RR_RX_STOP; + else if (c == START_CHAR(tty)) + ch->ch_send |= RR_RX_START; + + ch->ch_nd->nd_tx_ready = 1; + ch->ch_nd->nd_tx_work = 1; + + return; +} + + +static void dgrp_tty_input_start(struct tty_struct *tty) +{ + struct ch_struct *ch; + + if (!tty) + return; + + ch = ((struct un_struct *) tty->driver_data)->un_ch; + if (!ch) + return; + + ch->ch_send |= RR_RX_START; + ch->ch_send &= ~RR_RX_STOP; + (ch->ch_nd)->nd_tx_ready = 1; + (ch->ch_nd)->nd_tx_work = 1; + if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq)) + wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq); + +} + + +/* + * Hangup the port. Like a close, but don't wait for output + * to drain. + * + * How do we close all the channels that are open? + */ +static void dgrp_tty_hangup(struct tty_struct *tty) +{ + struct ch_struct *ch; + struct nd_struct *nd; + struct un_struct *un; + + if (!tty) + return; + + un = tty->driver_data; + if (!un) + return; + + ch = un->un_ch; + if (!ch) + return; + + nd = ch->ch_nd; + + if (C_HUPCL(tty)) { + /* LOWER DTR */ + ch->ch_mout &= ~DM_DTR; + /* Don't do this here */ + /* ch->ch_flag |= CH_HANGUP; */ + ch->ch_nd->nd_tx_ready = 1; + ch->ch_nd->nd_tx_work = 1; + if (waitqueue_active(&ch->ch_flag_wait)) + wake_up_interruptible(&ch->ch_flag_wait); + } + +} + +/************************************************************************/ +/* */ +/* TTY Initialization/Cleanup Functions */ +/* */ +/************************************************************************/ + +/* + * Uninitialize the TTY portion of the supplied node. Free all + * memory and resources associated with this node. Do it in reverse + * allocation order: this might possibly result in less fragmentation + * of memory, though I don't know this for sure. + */ +void +dgrp_tty_uninit(struct nd_struct *nd) +{ + char id[3]; + + ID_TO_CHAR(nd->nd_ID, id); + + if (nd->nd_ttdriver_flags & SERIAL_TTDRV_REG) { + tty_unregister_driver(nd->nd_serial_ttdriver); + + kfree(nd->nd_serial_ttdriver->ttys); + nd->nd_serial_ttdriver->ttys = NULL; + + put_tty_driver(nd->nd_serial_ttdriver); + nd->nd_ttdriver_flags &= ~SERIAL_TTDRV_REG; + } + + if (nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG) { + tty_unregister_driver(nd->nd_callout_ttdriver); + + kfree(nd->nd_callout_ttdriver->ttys); + nd->nd_callout_ttdriver->ttys = NULL; + + put_tty_driver(nd->nd_callout_ttdriver); + nd->nd_ttdriver_flags &= ~CALLOUT_TTDRV_REG; + } + + if (nd->nd_ttdriver_flags & XPRINT_TTDRV_REG) { + tty_unregister_driver(nd->nd_xprint_ttdriver); + + kfree(nd->nd_xprint_ttdriver->ttys); + nd->nd_xprint_ttdriver->ttys = NULL; + + put_tty_driver(nd->nd_xprint_ttdriver); + nd->nd_ttdriver_flags &= ~XPRINT_TTDRV_REG; + } +} + + + +/* + * Initialize the TTY portion of the supplied node. + */ +int +dgrp_tty_init(struct nd_struct *nd) +{ + char id[3]; + int rc; + int i; + + ID_TO_CHAR(nd->nd_ID, id); + + /* + * Initialize the TTDRIVER structures. + */ + + nd->nd_serial_ttdriver = alloc_tty_driver(CHAN_MAX); + if (!nd->nd_serial_ttdriver) + return -ENOMEM; + + sprintf(nd->nd_serial_name, "tty_dgrp_%s_", id); + + nd->nd_serial_ttdriver->owner = THIS_MODULE; + nd->nd_serial_ttdriver->name = nd->nd_serial_name; + nd->nd_serial_ttdriver->name_base = 0; + nd->nd_serial_ttdriver->major = 0; + nd->nd_serial_ttdriver->minor_start = 0; + nd->nd_serial_ttdriver->type = TTY_DRIVER_TYPE_SERIAL; + nd->nd_serial_ttdriver->subtype = SERIAL_TYPE_NORMAL; + nd->nd_serial_ttdriver->init_termios = DefaultTermios; + nd->nd_serial_ttdriver->driver_name = "dgrp"; + nd->nd_serial_ttdriver->flags = (TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV | + TTY_DRIVER_HARDWARE_BREAK); + + /* The kernel wants space to store pointers to tty_structs. */ + nd->nd_serial_ttdriver->ttys = + kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL); + if (!nd->nd_serial_ttdriver->ttys) + return -ENOMEM; + + tty_set_operations(nd->nd_serial_ttdriver, &dgrp_tty_ops); + + if (!(nd->nd_ttdriver_flags & SERIAL_TTDRV_REG)) { + /* + * Register tty devices + */ + rc = tty_register_driver(nd->nd_serial_ttdriver); + if (rc < 0) { + /* + * If errno is EBUSY, this means there are no more + * slots available to have us auto-majored. + * (Which is currently supported up to 256) + * + * We can still request majors above 256, + * we just have to do it manually. + */ + if (rc == -EBUSY) { + int i; + int max_majors = 1U << (32 - MINORBITS); + for (i = 256; i < max_majors; i++) { + nd->nd_serial_ttdriver->major = i; + rc = tty_register_driver(nd->nd_serial_ttdriver); + if (rc >= 0) + break; + } + /* Really fail now, since we ran out + * of majors to try. */ + if (i == max_majors) + return rc; + + } else { + return rc; + } + } + nd->nd_ttdriver_flags |= SERIAL_TTDRV_REG; + } + + nd->nd_callout_ttdriver = alloc_tty_driver(CHAN_MAX); + if (!nd->nd_callout_ttdriver) + return -ENOMEM; + + sprintf(nd->nd_callout_name, "cu_dgrp_%s_", id); + + nd->nd_callout_ttdriver->owner = THIS_MODULE; + nd->nd_callout_ttdriver->name = nd->nd_callout_name; + nd->nd_callout_ttdriver->name_base = 0; + nd->nd_callout_ttdriver->major = nd->nd_serial_ttdriver->major; + nd->nd_callout_ttdriver->minor_start = 0x40; + nd->nd_callout_ttdriver->type = TTY_DRIVER_TYPE_SERIAL; + nd->nd_callout_ttdriver->subtype = SERIAL_TYPE_CALLOUT; + nd->nd_callout_ttdriver->init_termios = DefaultTermios; + nd->nd_callout_ttdriver->driver_name = "dgrp"; + nd->nd_callout_ttdriver->flags = (TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV | + TTY_DRIVER_HARDWARE_BREAK); + + /* The kernel wants space to store pointers to tty_structs. */ + nd->nd_callout_ttdriver->ttys = + kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL); + if (!nd->nd_callout_ttdriver->ttys) + return -ENOMEM; + + tty_set_operations(nd->nd_callout_ttdriver, &dgrp_tty_ops); + + if (dgrp_register_cudevices) { + if (!(nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG)) { + /* + * Register cu devices + */ + rc = tty_register_driver(nd->nd_callout_ttdriver); + if (rc < 0) + return rc; + nd->nd_ttdriver_flags |= CALLOUT_TTDRV_REG; + } + } + + + nd->nd_xprint_ttdriver = alloc_tty_driver(CHAN_MAX); + if (!nd->nd_xprint_ttdriver) + return -ENOMEM; + + sprintf(nd->nd_xprint_name, "pr_dgrp_%s_", id); + + nd->nd_xprint_ttdriver->owner = THIS_MODULE; + nd->nd_xprint_ttdriver->name = nd->nd_xprint_name; + nd->nd_xprint_ttdriver->name_base = 0; + nd->nd_xprint_ttdriver->major = nd->nd_serial_ttdriver->major; + nd->nd_xprint_ttdriver->minor_start = 0x80; + nd->nd_xprint_ttdriver->type = TTY_DRIVER_TYPE_SERIAL; + nd->nd_xprint_ttdriver->subtype = SERIAL_TYPE_XPRINT; + nd->nd_xprint_ttdriver->init_termios = DefaultTermios; + nd->nd_xprint_ttdriver->driver_name = "dgrp"; + nd->nd_xprint_ttdriver->flags = (TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV | + TTY_DRIVER_HARDWARE_BREAK); + + /* The kernel wants space to store pointers to tty_structs. */ + nd->nd_xprint_ttdriver->ttys = + kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL); + if (!nd->nd_xprint_ttdriver->ttys) + return -ENOMEM; + + tty_set_operations(nd->nd_xprint_ttdriver, &dgrp_tty_ops); + + if (dgrp_register_prdevices) { + if (!(nd->nd_ttdriver_flags & XPRINT_TTDRV_REG)) { + /* + * Register transparent print devices + */ + rc = tty_register_driver(nd->nd_xprint_ttdriver); + if (rc < 0) + return rc; + nd->nd_ttdriver_flags |= XPRINT_TTDRV_REG; + } + } + + for (i = 0; i < CHAN_MAX; i++) { + struct ch_struct *ch = nd->nd_chan + i; + + ch->ch_nd = nd; + ch->ch_digi = digi_init; + ch->ch_edelay = 100; + ch->ch_custom_speed = 0; + ch->ch_portnum = i; + ch->ch_tun.un_ch = ch; + ch->ch_pun.un_ch = ch; + ch->ch_tun.un_type = SERIAL_TYPE_NORMAL; + ch->ch_pun.un_type = SERIAL_TYPE_XPRINT; + + init_waitqueue_head(&(ch->ch_flag_wait)); + init_waitqueue_head(&(ch->ch_sleep)); + + init_waitqueue_head(&(ch->ch_tun.un_open_wait)); + init_waitqueue_head(&(ch->ch_tun.un_close_wait)); + + init_waitqueue_head(&(ch->ch_pun.un_open_wait)); + init_waitqueue_head(&(ch->ch_pun.un_close_wait)); + tty_port_init(&ch->port); + tty_port_init(&ch->port); + } + return 0; +} |