From:     Digestifier <Linux-Admin-Request@senator-bedfellow.mit.edu>
To:       Linux-Admin@senator-bedfellow.mit.edu
Reply-To: Linux-Admin@senator-bedfellow.mit.edu
Date:     Mon, 27 Sep 93 09:13:50 EDT
Subject:  Linux-Admin Digest #79

Linux-Admin Digest #79, Volume #1                Mon, 27 Sep 93 09:13:50 EDT

Contents:
  [Q} HELP MULTIPORT SERIAL  NOT WORKING (sysop)

----------------------------------------------------------------------------

From: root@suburbia.apana.org.au (sysop)
Crossposted-To: comp.os.linux.help
Subject: [Q} HELP MULTIPORT SERIAL  NOT WORKING
Date: 27 Sep 93 21:00:38 GMT


Please help.

I have 2 four port cards in my linux machine, running
as  public access unix machine.

Users connect fine, but if they don't logout proberly,
ie, turn their modem off mid session, then their jobs 
don't get hung up ( ie, their process hang around. )

I am running MCC pl10+. I got pl13, and installed that.

I am including relevant files below, tell me if you think
I am doing anything wrong.

(I've posted once, and 4 people wrote back saying they had the
same problems... Help!)


   1 uugetty.ttyS0


# [ put this file in /etc/default/uugetty.<line> ]
#
# sample uugetty configuration file for a Hayes compatible modem to allow
# incoming modem connections
#
# this config file sets the modem to autoanswer.

# alternate lockfile to check... if this lockfile exists, then uugetty is
# restarted so that the modem is re-initialized
ALTLOCK=cua0
LOGIN=/bin/login
ISSUE=/etc/issue
CLEAR=NO
ALTLINE=cua0
INITLINE=cua0
# timeout to disconnect if idle...
TIMEOUT=180
HANGUP=YES
# modem initialization string... Sets the modem to auto-answer
#
# format: <expect> <send> ... (chat sequence)
INIT="" \d+++\dAT\r OK\r\n ATH0\r OK\r\n AT\sM0\sB2\sE1\sQ0\sV1\sX4\s&c1\s&d1\sS0=1\r OK\r\n

# this line sets the time to delay before sending the login banner
DELAY=1


   rc.serial


#!/bin/sh
#
# /etc/rc.serial 
#       Initializes the serial ports on your system
#

# Standard flags you want your serial devices to have
# Examples: SAK, pgrp_lockout, session_lockout
#
STD_FLAGS="session_lockout termios_restore hup_notify"

SETSERIAL=/bin/setserial

echo -n "Configuring the stupid damn serial ports...."

${SETSERIAL} /dev/cua0 ${STD_FLAGS} autoconfig
${SETSERIAL} /dev/cua1 ${STD_FLAGS} autoconfig
${SETSERIAL} /dev/cua2 ${STD_FLAGS} autoconfig
${SETSERIAL} /dev/cua3 ${STD_FLAGS} autoconfig spd_hi

${SETSERIAL} /dev/cua4 ${STD_FLAGS} autoconfig 
${SETSERIAL} /dev/cua5 ${STD_FLAGS} autoconfig
${SETSERIAL} /dev/cua6 ${STD_FLAGS} autoconfig
${SETSERIAL} /dev/cua7 ${STD_FLAGS} autoconfig

###########################################################
#
# Print the results of the serial configuration process
#
###########################################################

echo "done. There."
echo
echo "Hate it."
echo
echo "Enough of this."
echo
echo "Waaah."
echo

${SETSERIAL} -a /dev/cua? 


   2 inittab


# Default runlevel.
#
id:4:initdefault:

# System initialization (runs when system boots).
#
si::sysinit:/etc/update &

# Script to run when going multi user.
#
rc:123456:wait:/etc/rc

# Script to run when going single user.
#
su:S:wait:/etc/rc.single

# Run the cron deamon.
#
#cr::boot:/usr/bin/cron

# The gettys in multi user mode on consoles and serial lines.
#

1:456:respawn:/etc/getty tty1 DT9600 console
2:456:respawn:/etc/getty tty2 DT9600 console
3:456:respawn:/etc/getty tty3 DT9600 console
4:456:respawn:/etc/getty tty4 DT9600 console
5:456:respawn:/etc/getty tty5 DT9600 console
6:456:respawn:/etc/getty tty6 DT9600 console
7:456:respawn:/etc/getty tty7 DT9600 console
8:456:respawn:/etc/getty tty8 DT9600 console

S0:456:respawn:/etc/uugetty ttyS0 2400 vt100
S1:456:respawn:/etc/uugetty ttyS1 2400 vt100
S2:456:respawn:/etc/uugetty ttyS2 2400 vt100
S4:456:respawn:/etc/uugetty ttyS4 2400 vt100
S5:456:respawn:/etc/uugetty ttyS5 2400 vt100
S6:456:respawn:/etc/getty ttyS6 DT9600 vt100

# This script is executed when you want to start up x automatically.
#
x1:6:wait:/etc/rc.x11

# What to do when CTRL-ALT-DEL is pressed.
#
ca::ctrlaltdel:/bin/shutdown -r now

# What to do when power fails (shutdown to single user).
#
pf::powerfail:/etc/shutdown -f +5 "THE POWER IS FAILING"
#
# If power is back before shutdown, cancel the running shutdown.
#
pg:0123456:powerokwait:/etc/shutdown -c "THE POWER IS BACK"
#
# If power comes back in single user mode, return to multi user mode.
#
ps:S:powerokwait:/etc/init 4

# End of /etc/inittab



   1 gettydefs


# Virtual Console entry
VC# B9600 SANE # B9600 SANE -ISTRIP #@S login: #VC

# 38400 fixed baud Dumb Terminal entry
DT38400# B38400 CS8 # B38400 SANE -ISTRIP #@S login: #DT38400

# 19200 fixed baud Dumb Terminal entry
DT19200# B19200 CS8 # B19200 SANE -ISTRIP #@S login: #DT19200

# 9600 baud Dumb Terminal entry
DT9600# B9600 CS8 # B9600 SANE -ISTRIP #@S login: #DT9600

2400# B2400 CS8 # B2400 SANE -ISTRIP HUPCL #@S login: #1200

1200# B1200 CS8 # B1200 SANE -ISTRIP HUPCL #@S login: #2400


  49 serial.c


/*
 *  linux/kernel/serial.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 *  Extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92.  Now
 *  much more extensible to support other serial cards based on the
 *  16450/16550A UART's.  Added support for the AST FourPort and the
 *  Accent Async board.  
 *
 *  set_serial_info fixed to set the flags, custom divisor, and uart
 *      type fields.  Fix suggested by Michael K. Johnson 12/12/92.
 *
 * This module exports the following rs232 io functions:
 *
 *      long rs_init(long);
 *      int  rs_open(struct tty_struct * tty, struct file * filp)
 */

#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/tty.h>
#include <linux/serial.h>
#include <linux/interrupt.h>
#include <linux/config.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/ptrace.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/bitops.h>

/*
 * Serial driver configuration section.  Here are the various options:
 *
 * CONFIG_AUTO_IRQ
 *              Enables automatic IRQ detection.  I've put in some
 *              fixes to this which should make this work much more
 *              cleanly than it used to in 0.98pl2-6.  It should be
 *              much less vulnerable to false IRQ's now.
 * 
 * CONFIG_AST_FOURPORT
 *              Enables support for the AST Fourport serial port.
 * 
 * CONFIG_ACCENT_ASYNC
 *              Enables support for the Accent Async 4 port serial
 *              port.
 *
 * CONFIG_HUB6
 *              Enables support for the venerable Bell Technologies
 *              HUB6 card.
 */

#undef ISR_HACK

#define WAKEUP_CHARS (3*TTY_BUF_SIZE/4)

/*
 * rs_event             - Bitfield of serial lines that events pending
 *                              to be processed at the next clock tick.
 *
 * We assume here that int's are 32 bits, so an array of two gives us
 * 64 lines, which is the maximum we can support.
 */
static int rs_event[2];

static struct async_struct *IRQ_ports[16];
static int IRQ_active;
static unsigned long IRQ_timer[16];
static int IRQ_timeout[16];
static volatile int rs_irq_triggered;
static volatile int rs_triggered;
static int rs_wild_int_mask;

static void autoconfig(struct async_struct * info);
        
/*
 * This assumes you have a 1.8432 MHz clock for your UART.
 *
 * It'd be nice if someone built a serial card with a 24.576 MHz
 * clock, since the 16550A is capable of handling a top speed of 1.5
 * megabits/second; but this requires the faster clock.
 */
#define BASE_BAUD ( 1843200 / 16 )

#ifdef CONFIG_AUTO_IRQ
#define AUTO_IRQ_FLAG ASYNC_AUTO_IRQ
#else
#define AUTO_IRQ_FLAG 0
#endif

/* Standard COM flags (except for COM4, because of the 8514 problem) */
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST | AUTO_IRQ_FLAG)
#define STD_COM4_FLAGS (ASYNC_BOOT_AUTOCONF | AUTO_IRQ_FLAG)

#ifdef CONFIG_AST_FOURPORT
#define FOURPORT_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_FOURPORT | AUTO_IRQ_FLAG)
#else
#define FOURPORT_FLAGS (ASYNC_FOURPORT | AUTO_IRQ_FLAG)
#endif

#ifdef CONFIG_ACCENT_ASYNC
#define ACCENT_FLAGS (ASYNC_BOOT_AUTOCONF | AUTO_IRQ_FLAG)
#else
#define ACCENT_FLAGS AUTO_IRQ_FLAG
#endif

#ifdef CONFIG_BOCA
#define BOCA_FLAGS (ASYNC_BOOT_AUTOCONF | AUTO_IRQ_FLAG)
#else
#define BOCA_FLAGS AUTO_IRQ_FLAG
#endif

#ifdef CONFIG_HUB6
#define HUB6_FLAGS (ASYNC_BOOT_AUTOCONF)
#else
#define HUB6_FLAGS 0
#endif
        
/*
 * The following define the access methods for the HUB6 card. All
 * access is through two ports for all 24 possible chips. The card is
 * selected through the high 2 bits, the port on that card with the
 * "middle" 3 bits, and the register on that port with the bottom
 * 3 bits.
 *
 * While the access port and interrupt is configurable, the default
 * port locations are 0x302 for the port control register, and 0x303
 * for the data read/write register. Normally, the interrupt is at irq3
 * but can be anything from 3 to 7 inclusive. Note tht using 3 will
 * require disabling com2.
 */

#define C_P(card,port) (((card)<<6|(port)<<3) + 1)



struct async_struct rs_table[] = {
        /* UART CLK   PORT IRQ     FLAGS        */
        { BASE_BAUD, 0x100, 3 },        /* ttyS0 */
        { BASE_BAUD, 0x108, 3 },        /* ttyS1 */
        { BASE_BAUD, 0x110, 3 },        /* ttyS2 */
        { BASE_BAUD, 0x118, 3 },        /* ttyS3 */
        { BASE_BAUD, 0x280, 4, FOURPORT_FLAGS },        /* ttyS4 */
        { BASE_BAUD, 0x288, 4, FOURPORT_FLAGS },        /* ttyS5 */
        { BASE_BAUD, 0x290, 4, FOURPORT_FLAGS },        /* ttyS6 */
        { BASE_BAUD, 0x298, 4, FOURPORT_FLAGS },        /* ttyS7 */
};

#define NR_PORTS        (sizeof(rs_table)/sizeof(struct async_struct))

/*
 * This is used to figure out the divsor speeds and the timeouts
 */
static int baud_table[] = {
        0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
        9600, 19200, 38400, 57600, 115200, 0 };

static void rs_throttle(struct tty_struct * tty, int status);

static inline unsigned int serial_in(struct async_struct *info, int offset)
{
    if (info->hub6) {
        outb(info->hub6 - 1 + offset, info->port);
        return inb(info->port+1);
    } else
        return inb(info->port + offset);
}

static inline unsigned int serial_inp(struct async_struct *info, int offset)
{
    if (info->hub6) {
        outb(info->hub6 - 1 + offset, info->port);
        return inb_p(info->port+1);
    } else
        return inb_p(info->port + offset);
}

static inline void serial_out(struct async_struct *info, int offset, int value)
{
    if (info->hub6) {
        outb(info->hub6 - 1 + offset, info->port);
        outb(value, info->port+1);
    } else
        outb(value, info->port+offset);
}

static inline void serial_outp(struct async_struct *info, int offset,
                               int value)
{
    if (info->hub6) {
        outb(info->hub6 - 1 + offset, info->port);
        outb_p(value, info->port+1);
    } else
        outb_p(value, info->port+offset);
}

/*
 * ------------------------------------------------------------
 * rs_stop() and rs_start()
 *
 * This routines are called before setting or resetting tty->stopped.
 * They enable or disable transmitter interrupts, as necessary.
 * ------------------------------------------------------------
 */
static void rs_stop(struct tty_struct *tty)
{
        struct async_struct *info;
        
        info = rs_table + DEV_TO_SL(tty->line);
        
        info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;
#ifdef ISR_HACK
        serial_out(info, UART_IER, info->IER);
#endif
}

static void rs_start(struct tty_struct *tty)
{
        struct async_struct *info;
        
        info = rs_table + DEV_TO_SL(tty->line);
        
        info->IER = (UART_IER_MSI | UART_IER_RLSI |
                     UART_IER_THRI | UART_IER_RDI);
#ifdef ISR_HACK
        serial_out(info, UART_IER, info->IER);
#endif
}

/*
 * ----------------------------------------------------------------------
 *
 * Here starts the interrupt handling routines.  All of the following
 * subroutines are declared as inline and are folded into
 * rs_interrupt().  They were separated out for readability's sake.
 *
 * Note: rs_interrupt() is a "fast" interrupt, which means that it
 * runs with interrupts turned off.  People who may want to modify
 * rs_interrupt() should try to keep the interrupt handler as fast as
 * possible.  After you are done making modifications, it is not a bad
 * idea to do:
 * 
 * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c
 *
 * and look at the resulting assemble code in serial.s.
 *
 *                              - Ted Ts'o (tytso@mit.edu), 7-Mar-93
 * -----------------------------------------------------------------------
 */

/*
 * This is the serial driver's interrupt routine while we are probing
 * for submarines.
 */
static void rs_probe(int irq)
{
        rs_irq_triggered = irq;
        rs_triggered |= 1 << irq;
        return;
}

/*
 * This routine is used by the interrupt handler to schedule
 * processing in the software interrupt portion of the driver.
 */
static inline void rs_sched_event(struct async_struct *info,
                                  int event)
{
        info->event |= 1 << event;
        set_bit(info->line, rs_event);
        mark_bh(SERIAL_BH);
}

static inline void receive_chars(struct async_struct *info,
                                 int *status)
{
        struct tty_queue * queue;
        int head, tail, ch;

/*
 * Just like the LEFT(x) macro, except it uses the loal tail
 * and head variables.
 */
#define VLEFT ((tail-head-1)&(TTY_BUF_SIZE-1))

        queue = &info->tty->read_q;
        head = queue->head;
        tail = queue->tail;
        do {
                ch = serial_inp(info, UART_RX);
                /*
                 * There must be at least 2 characters
                 * free in the queue; otherwise we punt.
                 */
                if (VLEFT < 2)
                        break;
                if (*status & info->read_status_mask) {
                        set_bit(head, &info->tty->readq_flags);
                        if (*status & (UART_LSR_BI)) {
                                queue->buf[head++]= TTY_BREAK;
                                rs_sched_event(info, RS_EVENT_BREAK);
                        } else if (*status & UART_LSR_PE)
                                queue->buf[head++]= TTY_PARITY;
                        else if (*status & UART_LSR_FE)
                                queue->buf[head++]= TTY_FRAME;
                        head &= TTY_BUF_SIZE-1;
                }
                queue->buf[head++] = ch;
                head &= TTY_BUF_SIZE-1;
        } while ((*status = serial_inp(info, UART_LSR)) & UART_LSR_DR);
        queue->head = head;
        if ((VLEFT < RQ_THRESHOLD_LW) && !set_bit(TTY_RQ_THROTTLED,
                                                  &info->tty->flags)) 
                rs_throttle(info->tty, TTY_THROTTLE_RQ_FULL);
        rs_sched_event(info, RS_EVENT_READ_PROCESS);
#ifdef SERIAL_DEBUG_INTR
        printk("DR...");
#endif
}

static inline void transmit_chars(struct async_struct *info, int *done_work)
{
        struct tty_queue * queue;
        int head, tail, count;
        
        queue = &info->tty->write_q;
        head = queue->head;
        tail = queue->tail;
        if (head==tail && !info->x_char) {
                info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;
#ifdef ISR_HACK
                serial_out(info, UART_IER, info->IER);
#endif
                return;
        }
        count = info->xmit_fifo_size;
        if (info->x_char) {
                serial_outp(info, UART_TX, info->x_char);
                info->x_char = 0;
                count--;
        }
        while (count-- && (tail != head)) {
                serial_outp(info, UART_TX, queue->buf[tail++]);
                tail &= TTY_BUF_SIZE-1;
        }
        queue->tail = tail;
        if (VLEFT > WAKEUP_CHARS) {
                rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
                if (info->tty->write_data_cnt) {
                        set_bit(info->tty->line, &tty_check_write);
                        mark_bh(TTY_BH);
                }
        }
#ifdef SERIAL_DEBUG_INTR
        printk("THRE...");
#endif
        (*done_work)++;
}

static inline int check_modem_status(struct async_struct *info)
{
        int     status;
        
        status = serial_in(info, UART_MSR);
                
        if ((status & UART_MSR_DDCD) && !C_LOCAL(info->tty)) {
#ifdef SERIAL_DEBUG_INTR
                printk("DDCD...");
#endif
                if (status & UART_MSR_DCD)
                        rs_sched_event(info, RS_EVENT_OPEN_WAKEUP);
                else if (!((info->flags & ASYNC_CALLOUT_ACTIVE) &&
                           (info->flags & ASYNC_CALLOUT_NOHUP))) 
                        rs_sched_event(info, RS_EVENT_HANGUP);
        }
        if (C_RTSCTS(info->tty)) {
                if (info->tty->stopped) {
                        if (status & UART_MSR_CTS) {
#ifdef SERIAL_DEBUG_INTR
                                printk("CTS tx start...");
#endif
                                info->tty->stopped = 0;
                                rs_start(info->tty);
                                return 1;
                        }
                } else {
                        if (!(status & UART_MSR_CTS)) {
#ifdef SERIAL_DEBUG_INTR
                                printk("CTS tx stop...");
#endif
                                info->tty->stopped = 1;
                                rs_stop(info->tty);
                        }
                }
        }
        return 0;
}

static inline void figure_RS_timer(void)
{
        int     timeout = 6000; /* 60 seconds; really big :-) */
        int     i, mask;
        
        if (!IRQ_active)
                return;
        for (i=0, mask = 1; mask <= IRQ_active; i++, mask <<= 1) {
                if (!(mask & IRQ_active))
                        continue;
                if (IRQ_timer[i] < timeout)
                        timeout = IRQ_timer[i];
        }
        timer_table[RS_TIMER].expires = jiffies + timeout;
        timer_active |= 1 << RS_TIMER;
}


/*
 * This is the serial driver's generic interrupt routine
 */
static void rs_interrupt(int irq)
{
        int status;
        struct async_struct * info;
        int done, done_work, pass_number, recheck_count;

        rs_irq_triggered = irq;
        rs_triggered |= 1 << irq;
        
        info = IRQ_ports[irq];
        done = 1;
        done_work = 0;
        pass_number = 0;
        while (info) {
                if (info->tty &&
                    (!pass_number ||
                     !(serial_inp(info, UART_IIR) & UART_IIR_NO_INT))) {
                        done = 0;
                        status = serial_inp(info, UART_LSR);
                        if (status & UART_LSR_DR) {
                                receive_chars(info, &status);
                                done_work++;
                        }
                recheck_count = 0;
                recheck_write:
                        if ((status & UART_LSR_THRE) &&
                            !info->tty->stopped) {
                                transmit_chars(info, &done_work);
                        }
                        if (check_modem_status(info) &&
                            (recheck_count++ <= 64))
                                goto recheck_write;
#ifdef SERIAL_DEBUG_INTR
                        if (recheck_count > 16)
                                printk("recheck_count = %d\n", recheck_count);
#endif
                }
#ifdef ISR_HACK
                serial_outp(info, UART_IER, 0);
                serial_out(info, UART_IER, info->IER);
#endif
                
                info = info->next_port;
                if (!info && !done) {
                        info = IRQ_ports[irq];
                        done = 1;
                        if (pass_number++ > 64)
                                break;          /* Prevent infinite loops */
                }
        }
        if ((info = IRQ_ports[irq]) != NULL) {
#ifdef 0
                do {
                        serial_outp(info, UART_IER, 0);
                        serial_out(info, UART_IER, info->IER);
                        info = info->next_port;
                } while (info);
#endif                  
                if (irq && !done_work)
                        IRQ_timer[irq] = jiffies + 1500;
                else
                        IRQ_timer[irq] = jiffies + IRQ_timeout[irq];
                IRQ_active |= 1 << irq;
        }
        figure_RS_timer();
}

/*
 * -------------------------------------------------------------------
 * Here ends the serial interrupt routines.
 * -------------------------------------------------------------------
 */

/*
 * This routine is called when we receive a break on a serial line.
 * It is executed out of the software interrupt routine.
 */
static inline void handle_rs_break(struct async_struct *info)
{
        if (info->flags & ASYNC_SAK)
                do_SAK(info->tty);
                
        if (I_BRKINT(info->tty)) {
                flush_input(info->tty);
                flush_output(info->tty);
                if (info->tty->pgrp > 0)
                        kill_pg(info->tty->pgrp, SIGINT,1);
        }
}

/*
 * This routine is used to handle the "bottom half" processing for the
 * serial driver, known also the "software interrupt" processing.
 * This processing is done at the kernel interrupt level, after the
 * rs_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON.  This
 * is where time-consuming activities which can not be done in the
 * interrupt driver proper are done; the interrupt driver schedules
 * them using rs_sched_event(), and they get done here.
 */
static void do_softint(void *unused)
{
        int                     i;
        struct async_struct     *info;
        
        for (i = 0, info = rs_table; i < NR_PORTS; i++,info++) {
                if (clear_bit(i, rs_event)) {
                        if (!info->tty) 
                                continue;
                        if (clear_bit(RS_EVENT_READ_PROCESS, &info->event)) {
                                TTY_READ_FLUSH(info->tty);
                        }
                        if (clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) {
                                wake_up_interruptible(&info->tty->write_q.proc_list);
                        }
                        if (clear_bit(RS_EVENT_HANGUP, &info->event)) {
                                tty_hangup(info->tty);
                                wake_up_interruptible(&info->open_wait);
                                info->flags &= ~(ASYNC_NORMAL_ACTIVE|
                                                 ASYNC_CALLOUT_ACTIVE);
                        }
                        if (clear_bit(RS_EVENT_BREAK, &info->event))
                                handle_rs_break(info);
                        if (clear_bit(RS_EVENT_OPEN_WAKEUP, &info->event)) {
                                wake_up_interruptible(&info->open_wait);
                        }
                }
        }
}

/*
 * This subroutine is called when the RS_TIMER goes off.  It is used
 * by the serial driver to run the rs_interrupt routine at certain
 * intervals, either because a serial interrupt might have been lost,
 * or because (in the case of IRQ=0) the serial port does not have an
 * interrupt, and is being checked only via the timer interrupts.
 */
static void rs_timer(void)
{
        int     i, mask;
        int     timeout = 0;

        for (i = 0, mask = 1; mask <= IRQ_active; i++, mask <<= 1) {
                if ((mask & IRQ_active) && (IRQ_timer[i] <= jiffies)) {
                        IRQ_active &= ~mask;
                        cli();
                        rs_interrupt(i);
                        sti();
                }
                if (mask & IRQ_active) {
                        if (!timeout || (IRQ_timer[i] < timeout))
                                timeout = IRQ_timer[i];
                }
        }
        if (timeout) {
                timer_table[RS_TIMER].expires = timeout;
                timer_active |= 1 << RS_TIMER;
        }
}

/*
 * ---------------------------------------------------------------
 * Low level utility subroutines for the serial driver:  routines to
 * figure out the appropriate timeout for an interrupt chain, routines
 * to initialize and startup a serial port, and routines to shutdown a
 * serial port.  Useful stuff like that.
 * ---------------------------------------------------------------
 */

/*
 * Grab all interrupts in preparation for doing an automatic irq
 * detection.  dontgrab is a mask of irq's _not_ to grab.  Returns a
 * mask of irq's which were grabbed and should therefore be freed
 * using free_all_interrupts().
 */
static int grab_all_interrupts(int dontgrab)
{
        int                     irq_lines = 0;
        int                     i, mask;
        struct sigaction        sa;
        
        sa.sa_handler = rs_probe;
        sa.sa_flags = (SA_INTERRUPT);
        sa.sa_mask = 0;
        sa.sa_restorer = NULL;
        
        for (i = 0, mask = 1; i < 16; i++, mask <<= 1) {
                if (!(mask & dontgrab) && !irqaction(i, &sa)) {
                        irq_lines |= mask;
                }
        }
        return irq_lines;
}

/*
 * Release all interrupts grabbed by grab_all_interrupts
 */
static void free_all_interrupts(int irq_lines)
{
        int     i;
        
        for (i = 0; i < 16; i++) {
                if (irq_lines & (1 << i))
                        free_irq(i);
        }
}

/*
 * This routine figures out the correct timeout for a particular IRQ.
 * It uses the smallest timeout of all of the serial ports in a
 * particular interrupt chain.
 */
static void figure_IRQ_timeout(int irq)
{
        struct  async_struct    *info;
        int     timeout = 6000; /* 60 seconds === a long time :-) */

        info = IRQ_ports[irq];
        if (!info) {
                IRQ_timeout[irq] = 6000;
                return;
        }
        while (info) {
                if (info->timeout < timeout)
                        timeout = info->timeout;
                info = info->next_port;
        }
        if (!irq)
                timeout = timeout / 2;
        IRQ_timeout[irq] = timeout ? timeout : 1;
}

static inline void unlink_port(struct async_struct *info)
{
#ifdef SERIAL_DEBUG_OPEN
        printk("unlinking serial port %d from irq %d....", info->line,
               info->irq);
#endif
        if (info->next_port)
                info->next_port->prev_port = info->prev_port;
        if (info->prev_port)
                info->prev_port->next_port = info->next_port;
        else
                IRQ_ports[info->irq] = info->next_port;
        figure_IRQ_timeout(info->irq);
}

static inline void link_port(struct async_struct *info)
{
#ifdef SERIAL_DEBUG_OPEN
        printk("linking serial port %d into irq %d...", info->line,
               info->irq);
#endif
        info->prev_port = 0;
        info->next_port = IRQ_ports[info->irq];
        if (info->next_port)
                info->next_port->prev_port = info;
        IRQ_ports[info->irq] = info;
        figure_IRQ_timeout(info->irq);
}

static void startup(struct async_struct * info)
{
        unsigned short ICP;
        unsigned long flags;

        save_flags(flags); cli();

        /*
         * First, clear the FIFO buffers and disable them
         */
        if (info->type == PORT_16550A)
                serial_outp(info, UART_FCR, UART_FCR_CLEAR_CMD);

        /*
         * Next, clear the interrupt registers.
         */
        (void)serial_inp(info, UART_LSR);
        (void)serial_inp(info, UART_RX);
        (void)serial_inp(info, UART_IIR);
        (void)serial_inp(info, UART_MSR);

        /*
         * Now, initialize the UART 
         */
        serial_outp(info, UART_LCR, UART_LCR_WLEN8);    /* reset DLAB */
        if (info->flags & ASYNC_FOURPORT) 
                serial_outp(info, UART_MCR, UART_MCR_DTR | UART_MCR_RTS);
        else
                serial_outp(info, UART_MCR,
                            UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
        
        /*
         * Enable FIFO's if necessary
         */
        if (info->type == PORT_16550A) {
                serial_outp(info, UART_FCR, UART_FCR_SETUP_CMD);
                info->xmit_fifo_size = 16;
        } else {
                info->xmit_fifo_size = 1;
        }

        /*
         * Finally, enable interrupts
         */
#ifdef ISR_HACK
        info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;
        serial_outp(info, UART_IER, info->IER); /* enable interrupts */
#else
        info->IER = (UART_IER_MSI | UART_IER_RLSI |
                     UART_IER_THRI | UART_IER_RDI);
        serial_outp(info, UART_IER, info->IER); /* enable all intrs */
#endif
        if (info->flags & ASYNC_FOURPORT) {
                /* Enable interrupts on the AST Fourport board */
                ICP = (info->port & 0xFE0) | 0x01F;
                outb_p(0x80, ICP);
                (void) inb_p(ICP);
        }

        /*
         * And clear the interrupt registers again for luck.
         */
        (void)serial_inp(info, UART_LSR);
        (void)serial_inp(info, UART_RX);
        (void)serial_inp(info, UART_IIR);
        (void)serial_inp(info, UART_MSR);

        info->flags |= ASYNC_INITIALIZED;
        if (info->tty)
                clear_bit(TTY_IO_ERROR, &info->tty->flags);
        /*
         * Set up parity check flag
         */
        if (info->tty && info->tty->termios && I_INPCK(info->tty))
                info->read_status_mask = (UART_LSR_OE | UART_LSR_BI |
                                          UART_LSR_FE | UART_LSR_PE);
        else
                info->read_status_mask = (UART_LSR_OE | UART_LSR_BI |
                                          UART_LSR_FE);
        restore_flags(flags);
}

/*
 * This routine shutsdown a serial port; interrupts are disabled, and
 * DTR is dropped if the hangup on close termio flag is on.
 */
static void shutdown(struct async_struct * info)
{
        unsigned long flags;

        save_flags(flags); cli();
        info->IER = 0;
        serial_outp(info, UART_IER, 0x00);      /* disable all intrs */
        if (info->flags & ASYNC_FOURPORT) {
                /* reset interrupts on the AST Fourport board */
                (void) inb((info->port & 0xFE0) | 0x01F);
        }
        if (info->tty && !(info->tty->termios->c_cflag & HUPCL))
                serial_outp(info, UART_MCR, UART_MCR_DTR);
        else
                /* reset DTR,RTS,OUT_2 */               
                serial_outp(info, UART_MCR, 0x00);
        serial_outp(info, UART_FCR, UART_FCR_CLEAR_CMD); /* disable FIFO's */
        (void)serial_in(info, UART_RX);    /* read data port to reset things */
        info->flags &= ~ASYNC_INITIALIZED;
        if (info->tty)
                set_bit(TTY_IO_ERROR, &info->tty->flags);
        restore_flags(flags);
}

/*
 * This routine is called to set the UART divisor registers to match
 * the specified baud rate for a serial port.
 */
static void change_speed(unsigned int line)
{
        struct async_struct * info;
        unsigned short port;
        int     quot = 0;
        unsigned cflag,cval,mcr;
        int     i;

        if (line >= NR_PORTS)
                return;
        info = rs_table + line;
        if (!info->tty || !info->tty->termios)
                return;
        cflag = info->tty->termios->c_cflag;
        if (!(port = info->port))
                return;
        i = cflag & CBAUD;
        if (i == 15) {
                if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
                        i += 1;
                if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
                        i += 2;
                if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
                        quot = info->custom_divisor;
        }
        if (quot) {
                info->timeout = ((info->xmit_fifo_size*HZ*15*quot) /
                                 info->baud_base) + 2;
        } else if (baud_table[i] == 134) {
                quot = (2*info->baud_base / 269);
                info->timeout = (info->xmit_fifo_size*HZ*30/269) + 2;
        } else if (baud_table[i]) {
                quot = info->baud_base / baud_table[i];
                info->timeout = (info->xmit_fifo_size*HZ*15/baud_table[i]) + 2;
        } else {
                quot = 0;
                info->timeout = 0;
        }
        cli();
        mcr = serial_in(info, UART_MCR);
        if (quot) 
                serial_out(info, UART_MCR, mcr | UART_MCR_DTR);
        else {
                serial_out(info, UART_MCR, mcr & ~UART_MCR_DTR);
                sti();
                return;
        }
        sti();
        /* byte size and parity */
        cval = cflag & (CSIZE | CSTOPB);
        cval >>= 4;
        if (cflag & PARENB)
                cval |= UART_LCR_PARITY;
        if (!(cflag & PARODD))
                cval |= UART_LCR_EPAR;
        cli();
        serial_outp(info, UART_LCR, cval | UART_LCR_DLAB);      /* set DLAB */
        serial_outp(info, UART_DLL, quot & 0xff);       /* LS of divisor */
        serial_outp(info, UART_DLM, quot >> 8);         /* MS of divisor */
        serial_outp(info, UART_LCR, cval);              /* reset DLAB */
        sti();
}

/*
 * ------------------------------------------------------------
 * rs_write() and friends
 * ------------------------------------------------------------
 */

/*
 * This routine is used by rs_write to restart transmitter interrupts,
 * which are disabled after we have a transmitter interrupt which went
 * unacknowledged because we had run out of data to transmit.
 * 
 * Note: this subroutine must be called with the interrupts *off*
 */
static inline void restart_port(struct async_struct *info)
{
        struct tty_queue * queue;
        int head, tail, count;
        
        if (!info)
                return;

        if (serial_inp(info, UART_LSR) & UART_LSR_THRE) {
                if (info->x_char) {
                        serial_outp(info, UART_TX, info->x_char);
                        info->x_char = 0;
                } else {
                        queue = &info->tty->write_q;
                        head = queue->head;
                        tail = queue->tail;
                        count = info->xmit_fifo_size;
                        while (count--) {
                                if (tail == head)
                                        break;
                                serial_outp(info, UART_TX, queue->buf[tail++]);
                                tail &= TTY_BUF_SIZE-1;
                        }
                        queue->tail = tail;
                }
        }
}       

/*
 * This routine gets called when tty_write has put something into
 * the write_queue.  
 */
void rs_write(struct tty_struct * tty)
{
        struct async_struct *info;

        if (!tty || tty->stopped)
                return;
        info = rs_table + DEV_TO_SL(tty->line);
        if (!info)
                return;
        cli();
        restart_port(info);
        info->IER = (UART_IER_MSI | UART_IER_RLSI |
                     UART_IER_THRI | UART_IER_RDI);
#ifdef ISR_HACK
        serial_out(info, UART_IER, info->IER);
#endif
        sti();
}

/*
 * ------------------------------------------------------------
 * rs_throttle()
 * 
 * This routine is called by the upper-layer tty layer to signal that
 * incoming characters should be throttled (and that the throttle
 * should be released).
 * ------------------------------------------------------------
 */
static void rs_throttle(struct tty_struct * tty, int status)
{
        struct async_struct *info;
        unsigned char mcr;
        unsigned long flags;

        save_flags(flags); cli();
#if SERIAL_DEBUG_THROTTLE
        printk("throttle tty%d: %d (%d, %d)....\n", DEV_TO_SL(tty->line),
               status, LEFT(&tty->read_q), LEFT(&tty->secondary));
#endif
        switch (status) {
        case TTY_THROTTLE_RQ_FULL:
                info = rs_table + DEV_TO_SL(tty->line);
                if (tty->termios->c_iflag & IXOFF) {
                        info->x_char = STOP_CHAR(tty);
                } else {
                        mcr = serial_inp(info, UART_MCR);
                        mcr &= ~UART_MCR_RTS;
                        serial_out(info, UART_MCR, mcr);
                }
                break;
        case TTY_THROTTLE_RQ_AVAIL:
                info = rs_table + DEV_TO_SL(tty->line);
                if (tty->termios->c_iflag & IXOFF) {
                        if (info->x_char)
                                info->x_char = 0;
                        else
                                info->x_char = START_CHAR(tty);
                } else {
                        mcr = serial_in(info, UART_MCR);
                        mcr |= UART_MCR_RTS;
                        serial_out(info, UART_MCR, mcr);
                }
                break;
        }
        restore_flags(flags);
}

/*
 * ------------------------------------------------------------
 * rs_ioctl() and friends
 * ------------------------------------------------------------
 */

static int get_serial_info(struct async_struct * info,
                           struct serial_struct * retinfo)
{
        struct serial_struct tmp;
  
        if (!retinfo)
                return -EFAULT;
        memset(&tmp, 0, sizeof(tmp));
        tmp.type = info->type;
        tmp.line = info->line;
        tmp.port = info->port;
        tmp.irq = info->irq;
        tmp.flags = info->flags;
        tmp.baud_base = info->baud_base;
        tmp.close_delay = info->close_delay;
        tmp.custom_divisor = info->custom_divisor;
        tmp.hub6 = info->hub6;
        memcpy_tofs(retinfo,&tmp,sizeof(*retinfo));
        return 0;
}

static int set_serial_info(struct async_struct * info,
                           struct serial_struct * new_info)
{
        struct serial_struct new_serial;
        struct async_struct old_info;
        unsigned int            i,change_irq,change_port;
        int                     retval;
        struct                  sigaction sa;

        if (!new_info)
                return -EFAULT;
        memcpy_fromfs(&new_serial,new_info,sizeof(new_serial));
        old_info = *info;

        change_irq = new_serial.irq != info->irq;
        change_port = (new_serial.port != info->port) || (new_serial.hub6 != info->hub6);

        if (!suser()) {
                if (change_irq || change_port ||
                    (new_serial.baud_base != info->baud_base) ||
                    (new_serial.type != info->type) ||
                    (new_serial.close_delay != info->close_delay) ||
                    ((new_serial.flags & ~ASYNC_USR_MASK) !=
                     (info->flags & ~ASYNC_USR_MASK)))
                        return -EPERM;
                info->flags = ((info->flags & ~ASYNC_USR_MASK) |
                               (new_serial.flags & ASYNC_USR_MASK));
                info->custom_divisor = new_serial.custom_divisor;
                new_serial.port = 0;    /* Prevent initialization below */
                goto check_and_exit;
        }

        if (new_serial.irq == 2)
                new_serial.irq = 9;

        if ((new_serial.irq > 15) || (new_serial.port > 0xffff) ||
            (new_serial.type < PORT_UNKNOWN) || (new_serial.type > PORT_MAX)) {
                return -EINVAL;
        }

        /* Make sure address is not already in use */
        for (i = 0 ; i < NR_PORTS; i++)
                if ((info != &rs_table[i]) &&
                    (rs_table[i].port == new_serial.port) && rs_table[i].type)
                        return -EADDRINUSE;

        /*
         * If necessary, first we try to grab the new IRQ for serial
         * interrupts.  (We have to do this early, since we may get an
         * error trying to do this.)
         */
        if (new_serial.port && new_serial.type && new_serial.irq &&
            (change_irq || !(info->flags & ASYNC_INITIALIZED))) {
                if (!IRQ_ports[new_serial.irq]) {
                        sa.sa_handler = rs_interrupt;
                        sa.sa_flags = (SA_INTERRUPT);
                        sa.sa_mask = 0;
                        sa.sa_restorer = NULL;
                        retval = irqaction(new_serial.irq,&sa);
                        if (retval)
                                return retval;
                }
        }

        if ((change_port || change_irq) && (info->count > 1))
                return -EBUSY;

        /*
         * OK, past this point, all the error checking has been done.
         * At this point, we start making changes.....
         */

        info->baud_base = new_serial.baud_base;
        info->flags = ((info->flags & ~ASYNC_FLAGS) |
                        (new_serial.flags & ASYNC_FLAGS));
        info->custom_divisor = new_serial.custom_divisor;
        info->type = new_serial.type;
        info->close_delay = new_serial.close_delay;

        if (change_port || change_irq) {
                /*
                 * We need to shutdown the serial port at the old
                 * port/irq combination.
                 */
                if (info->flags & ASYNC_INITIALIZED) {
                        shutdown(info);
                        unlink_port(info);
                        if (change_irq && info->irq && !IRQ_ports[info->irq])
                                free_irq(info->irq);
                }
                info->irq = new_serial.irq;
                info->port = new_serial.port;
                info->hub6 = new_serial.hub6;
        }
        
check_and_exit:
        if (info->port && info->type && 
            !(info->flags & ASYNC_INITIALIZED)) {
                /*
                 * Link the port into the new interrupt chain.
                 */
                link_port(info);
                startup(info);
                change_speed(info->line);
        } else if (((old_info.flags & ASYNC_SPD_MASK) !=
                    (info->flags & ASYNC_SPD_MASK)) ||
                   (old_info.custom_divisor != info->custom_divisor))
                change_speed(info->line);

        return 0;
}

static int get_modem_info(struct async_struct * info, unsigned int *value)
{
        unsigned char control, status;
        unsigned int result;

        cli();
        control = serial_in(info, UART_MCR);
        status = serial_in(info, UART_MSR);
        sti();
        result =  ((control & UART_MCR_RTS) ? TIOCM_RTS : 0)
                | ((control & UART_MCR_DTR) ? TIOCM_DTR : 0)
                | ((status  & UART_MSR_DCD) ? TIOCM_CAR : 0)
                | ((status  & UART_MSR_RI) ? TIOCM_RNG : 0)
                | ((status  & UART_MSR_DSR) ? TIOCM_DSR : 0)
                | ((status  & UART_MSR_CTS) ? TIOCM_CTS : 0);
        put_fs_long(result,(unsigned long *) value);
        return 0;
}

static int set_modem_info(struct async_struct * info, unsigned int cmd,
                          unsigned int *value)
{
        unsigned char control;
        unsigned int arg = get_fs_long((unsigned long *) value);
        
        cli();
        control = serial_in(info, UART_MCR);
        sti();

        switch (cmd) {
                case TIOCMBIS:
                        if (arg & TIOCM_RTS)
                                control |= UART_MCR_RTS;
                        if (arg & TIOCM_DTR)
                                control |= UART_MCR_DTR;
                        break;
                case TIOCMBIC:
                        if (arg & TIOCM_RTS)
                                control &= ~UART_MCR_RTS;
                        if (arg & TIOCM_DTR)
                                control &= ~UART_MCR_DTR;
                        break;
                case TIOCMSET:
                        control = (control & ~0x03)
                                | ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0)
                                | ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0);
                        break;
                default:
                        return -EINVAL;
        }
        cli();
        serial_out(info, UART_MCR, control);
        sti();
        return 0;
}

static int do_autoconfig(struct async_struct * info)
{
        struct sigaction        sa;
        int                     retval;
        
        if (!suser())
                return -EPERM;
        
        if (info->count > 1)
                return -EBUSY;
        
        if (info->flags & ASYNC_INITIALIZED) {
                shutdown(info);
                unlink_port(info);
                if (info->irq && !IRQ_ports[info->irq])
                        free_irq(info->irq);
        }

        cli();
        autoconfig(info);
        sti();

        if (info->port && info->type) {
                if (info->irq && !IRQ_ports[info->irq]) {
                        sa.sa_handler = rs_interrupt;
                        sa.sa_flags = (SA_INTERRUPT);
                        sa.sa_mask = 0;
                        sa.sa_restorer = NULL;
                        retval = irqaction(info->irq,&sa);
                        if (retval)
                                return retval;
                }
                link_port(info);
                startup(info);
                change_speed(info->line);
        }
        return 0;
}


/*
 * This routine sends a break character out the serial port.
 */
static void send_break( struct async_struct * info, int duration)
{
        if (!info->port)
                return;
        current->state = TASK_INTERRUPTIBLE;
        current->timeout = jiffies + duration;
        cli();
        serial_out(info, UART_LCR, serial_inp(info, UART_LCR) | UART_LCR_SBC);
        schedule();
        serial_out(info, UART_LCR, serial_inp(info, UART_LCR) & ~UART_LCR_SBC);
        sti();
}

/*
 * This routine returns a bitfield of "wild interrupts".  Basically,
 * any unclaimed interrupts which is flapping around.
 */
static int check_wild_interrupts(int doprint)
{
        int     i, mask;
        int     wild_interrupts = 0;
        int     irq_lines;
        unsigned long timeout;
        unsigned long flags;
        
        /* Turn on interrupts (they may be off) */
        save_flags(flags); sti();

        irq_lines = grab_all_interrupts(0);
        
        /*
         * Delay for 0.1 seconds -- we use a busy loop since this may 
         * occur during the bootup sequence
         */
        timeout = jiffies+10;
        while (timeout >= jiffies)
                ;
        
        rs_triggered = 0;       /* Reset after letting things settle */

        timeout = jiffies+10;
        while (timeout >= jiffies)
                ;
        
        for (i = 0, mask = 1; i < 16; i++, mask <<= 1) {
                if ((rs_triggered & (1 << i)) &&
                    (irq_lines & (1 << i))) {
                        wild_interrupts |= mask;
                        if (doprint)
                                printk("Wild interrupt?  (IRQ %d)\n", i);
                }
        }
        free_all_interrupts(irq_lines);
        restore_flags(flags);
        return wild_interrupts;
}

static int rs_ioctl(struct tty_struct *tty, struct file * file,
                    unsigned int cmd, unsigned long arg)
{
        int error, line;
        struct async_struct * info;

        line = DEV_TO_SL(tty->line);
        if (line < 0 || line >= NR_PORTS)
                return -ENODEV;
        info = rs_table + line;
        
        switch (cmd) {
                case TCSBRK:    /* SVID version: non-zero arg --> no break */
                        if (!arg)
                                send_break(info, HZ/4); /* 1/4 second */
                        return 0;
                case TCSBRKP:   /* support for POSIX tcsendbreak() */
                        send_break(info, arg ? arg*(HZ/10) : HZ/4);
                        return 0;
                case TIOCGSOFTCAR:
                        error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(unsigned int *));
                        if (error)
                                return error;
                        put_fs_long(C_LOCAL(tty) ? 1 : 0,
                                    (unsigned long *) arg);
                        return 0;
                case TIOCSSOFTCAR:
                        arg = get_fs_long((unsigned long *) arg);
                        tty->termios->c_cflag =
                                ((tty->termios->c_cflag & ~CLOCAL) |
                                 (arg ? CLOCAL : 0));
                        return 0;
                case TIOCMGET:
                        error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(unsigned int *));
                        if (error)
                                return error;
                        return get_modem_info(info, (unsigned int *) arg);
                case TIOCMBIS:
                case TIOCMBIC:
                case TIOCMSET:
                        return set_modem_info(info, cmd, (unsigned int *) arg);
                case TIOCGSERIAL:
                        error = verify_area(VERIFY_WRITE, (void *) arg,
                                                sizeof(struct serial_struct));
                        if (error)
                                return error;
                        return get_serial_info(info,
                                               (struct serial_struct *) arg);
                case TIOCSSERIAL:
                        return set_serial_info(info,
                                               (struct serial_struct *) arg);
                case TIOCSERCONFIG:
                        return do_autoconfig(info);

                case TIOCSERGWILD:
                        error = verify_area(VERIFY_WRITE, (void *) arg,
                                            sizeof(int));
                        if (error)
                                return error;
                        put_fs_long(rs_wild_int_mask, (unsigned long *) arg);
                        return 0;

                case TIOCSERSWILD:
                        if (!suser())
                                return -EPERM;
                        rs_wild_int_mask = get_fs_long((unsigned long *) arg);
                        if (rs_wild_int_mask < 0)
                                rs_wild_int_mask = check_wild_interrupts(0);
                        return 0;

                default:
                        return -EINVAL;
                }
        return 0;
}

static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios)
{
        struct async_struct *info;

        if (tty->termios->c_cflag == old_termios->c_cflag)
                return;

        info = &rs_table[DEV_TO_SL(tty->line)];

        change_speed(DEV_TO_SL(tty->line));
        
        if ((old_termios->c_cflag & CRTSCTS) &&
            !(tty->termios->c_cflag & CRTSCTS)) {
                tty->stopped = 0;
                rs_write(tty);
        }

        if (!(old_termios->c_cflag & CLOCAL) &&
            (tty->termios->c_cflag & CLOCAL))
                wake_up_interruptible(&info->open_wait);

        if (I_INPCK(tty))
                info->read_status_mask = (UART_LSR_OE | UART_LSR_BI |
                                          UART_LSR_FE | UART_LSR_PE);
        else
                info->read_status_mask = (UART_LSR_OE | UART_LSR_BI |
                                          UART_LSR_FE);
}

/*
 * ------------------------------------------------------------
 * rs_close()
 * 
 * This routine is called when the serial port gets closed.  First, we
 * wait for the last remaining data to be sent.  Then, we unlink its
 * async structure from the interrupt chain if necessary, and we free
 * that IRQ if nothing is left in the chain.
 * ------------------------------------------------------------
 */
static void rs_close(struct tty_struct *tty, struct file * filp)
{
        struct async_struct * info;
        int line;

        line = DEV_TO_SL(tty->line);
        if ((line < 0) || (line >= NR_PORTS))
                return;
        info = rs_table + line;
#ifdef SERIAL_DEBUG_OPEN
        printk("rs_close ttys%d, count = %d\n", info->line, info->count);
#endif
        if (--info->count > 0)
                return;
        tty->stopped = 0;               /* Force flush to succeed */
        rs_start(tty);
        wait_until_sent(tty);
        clear_bit(line, rs_event);
        info->event = 0;
        info->count = 0;
        if (info->blocked_open) {
                shutdown(info);
                if (info->close_delay) {
                        tty->count++; /* avoid race condition */
                        current->state = TASK_INTERRUPTIBLE;
                        current->timeout = jiffies + info->close_delay;
                        schedule();
                        tty->count--;
                }
                startup(info);
                info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
                if (tty->termios->c_cflag & CLOCAL)
                        wake_up_interruptible(&info->open_wait);
                return;
        }
        if (info->flags & ASYNC_INITIALIZED) {
                shutdown(info);
                unlink_port(info);
                if (info->irq && !IRQ_ports[info->irq])
                        free_irq(info->irq);
        }
        info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
        info->tty = 0;
}

/*
 * ------------------------------------------------------------
 * rs_open() and friends
 * ------------------------------------------------------------
 */
static int block_til_ready(struct tty_struct *tty, struct file * filp,
                           struct async_struct *info)
{
        struct wait_queue wait = { current, NULL };
        int             retval;
        int             do_clocal = C_LOCAL(tty);
        struct termios  orig_termios;
        int             tty_line = tty->line;
        
        /*
         * If this is a callout device, then just make sure the normal
         * device isn't being used.
         */
        if (MAJOR(filp->f_rdev) == 5) {
                if (info->flags & ASYNC_NORMAL_ACTIVE)
                        return -EBUSY;
                if ((info->flags & ASYNC_CALLOUT_ACTIVE) &&
                    (info->flags & ASYNC_SESSION_LOCKOUT) &&
                    (info->session != current->session))
                    return -EBUSY;
                if ((info->flags & ASYNC_CALLOUT_ACTIVE) &&
                    (info->flags & ASYNC_PGRP_LOCKOUT) &&
                    (info->pgrp != current->pgrp))
                    return -EBUSY;
                info->flags |= ASYNC_CALLOUT_ACTIVE;
                return 0;
        }
        
        /*
         * If non-blocking mode is set, then make the check up front
         * and then exit.
         */
        if (filp->f_flags & O_NONBLOCK) {
                if (info->flags & ASYNC_CALLOUT_ACTIVE)
                        return -EBUSY;
                info->flags |= ASYNC_NORMAL_ACTIVE;
                return 0;
        }

        /*
         * Block waiting for the carrier detect and the line to become
         * free (i.e., not in use by the callout).  While we are in
         * this loop, info->count is dropped by one, so that
         * rs_close() knows when to free things.  We restore it upon
         * exit, either normal or abnormal.
         */
        retval = 0;
        add_wait_queue(&info->open_wait, &wait);
#ifdef SERIAL_DEBUG_OPEN
        printk("block_til_ready before block: ttys%d, count = %d\n",
               info->line, info->count);
#endif
        info->count--;
        info->blocked_open++;
        memset(&orig_termios, 0, sizeof(orig_termios));
        if (tty_termios[tty_line])
                orig_termios = *tty_termios[tty_line];
        while (1) {
                cli();
                if (!(info->flags & ASYNC_CALLOUT_ACTIVE))
                        serial_out(info, UART_MCR,
                                   serial_inp(info, UART_MCR) | UART_MCR_DTR);
                sti();
                current->state = TASK_INTERRUPTIBLE;
                if (tty_hung_up_p(filp) && (info->flags & ASYNC_HUP_NOTIFY)) {
                        retval = -EAGAIN;
                        break;
                }
                if (!(info->flags & ASYNC_CALLOUT_ACTIVE) &&
                    (do_clocal || (serial_in(info, UART_MSR) &
                                   UART_MSR_DCD))) {
                        if (tty_hung_up_p(filp))
                                retval = -ERESTARTNOINTR;
                        break;
                }
                if (current->signal & ~current->blocked) {
                        retval = -ERESTARTSYS;
                        break;
                }
#ifdef SERIAL_DEBUG_OPEN
                printk("block_til_ready blocking: ttys%d, count = %d\n",
                       info->line, info->count);
#endif
                schedule();
        }
        current->state = TASK_RUNNING;
        remove_wait_queue(&info->open_wait, &wait);
        info->count++;
        info->blocked_open--;
#ifdef SERIAL_DEBUG_OPEN
        printk("block_til_ready after blocking: ttys%d, count = %d\n",
               info->line, info->count);
#endif
        if (retval)
                return retval;
        info->flags |= ASYNC_NORMAL_ACTIVE;
        if ((info->flags & ASYNC_TERMIOS_RESTORE) &&
            tty_termios[tty_line])
                *tty_termios[tty_line] = orig_termios;
        return 0;
}       

/*
 * This routine is called whenever a serial port is opened.  It
 * enables interrupts for a serial port, linking in its async structure into
 * the IRQ chain.   It also performs the serial-speicific
 * initalization for the tty structure.
 */
int rs_open(struct tty_struct *tty, struct file * filp)
{
        struct async_struct     *info;
        int                     retval, line;
        struct sigaction        sa;

        line = DEV_TO_SL(tty->line);
        if ((line < 0) || (line >= NR_PORTS))
                return -ENODEV;
        info = rs_table + line;
#ifdef SERIAL_DEBUG_OPEN
        printk("rs_open ttys%d, count = %d\n", info->line, info->count);
#endif
        info->count++;
        info->tty = tty;
        
        tty->write = rs_write;
        tty->close = rs_close;
        tty->ioctl = rs_ioctl;
        tty->throttle = rs_throttle;
        tty->set_termios = rs_set_termios;
        tty->stop = rs_stop;
        tty->start = rs_start;

        if (!(info->flags & ASYNC_INITIALIZED)) {
                if (!info->port || !info->type) {
                        set_bit(TTY_IO_ERROR, &tty->flags);
                        return 0;
                }
                if (info->irq && !IRQ_ports[info->irq]) {
                        sa.sa_handler = rs_interrupt;
                        sa.sa_flags = (SA_INTERRUPT);
                        sa.sa_mask = 0;
                        sa.sa_restorer = NULL;
                        retval = irqaction(info->irq,&sa);
                        if (retval)
                                return retval;
                }
                /*
                 * Link in port to IRQ chain
                 */
                link_port(info);
                startup(info);
                change_speed(info->line);
                if (!info->irq) {
                        IRQ_active |= info->line;
                        cli();
                        figure_RS_timer();
                        sti();
                }
        }

        retval = block_til_ready(tty, filp, info);
        if (retval)
                return retval;

        info->session = current->session;
        info->pgrp = current->pgrp;
        
        return 0;
}

/*
 * ---------------------------------------------------------------------
 * rs_init() and friends
 *
 * rs_init() is called at boot-time to initialize the serial driver.
 * ---------------------------------------------------------------------
 */

/*
 * This routine prints out the appropriate serial driver version
 * number, and identifies which options were configured into this
 * driver.
 */
static void show_serial_version(void)
{
        printk("Serial driver version 3.96 with");
#ifdef CONFIG_AST_FOURPORT
        printk(" AST_FOURPORT");
#define SERIAL_OPT
#endif
#ifdef CONFIG_ACCENT_ASYNC
        printk(" ACCENT_ASYNC");
#define SERIAL_OPT
#endif
#ifdef CONFIG_HUB6
        printk(" HUB-6");
#define SERIAL_OPT
#endif
#ifdef CONFIG_AUTO_IRQ
        printk (" AUTO_IRQ");
#define SERIAL_OPT
#endif
#ifdef SERIAL_OPT
        printk(" enabled\n");
#else
        printk(" no serial options enabled\n");
#endif
#undef SERIAL_OPT
}

/*
 * This routine is called by do_auto_irq(); it attempts to determine
 * which interrupt a serial port is configured to use.  It is not
 * fool-proof, but it works a large part of the time.
 */
static int get_auto_irq(struct async_struct *info)
{
        unsigned char save_MCR, save_IER, save_ICP=0;
        unsigned short ICP=0, port = info->port;
        unsigned long timeout;
        
        /*
         * Enable interrupts and see who answers
         */
        rs_irq_triggered = 0;
        cli();
        save_IER = serial_inp(info, UART_IER);
        save_MCR = serial_inp(info, UART_MCR);
        if (info->flags & ASYNC_FOURPORT)  {
                serial_outp(info, UART_MCR, UART_MCR_DTR | UART_MCR_RTS);
                serial_outp(info, UART_IER, 0x0f);      /* enable all intrs */
                ICP = (port & 0xFE0) | 0x01F;
                save_ICP = inb_p(ICP);
                outb_p(0x80, ICP);
                (void) inb_p(ICP);
        } else {
                serial_outp(info, UART_MCR,
                            UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
                serial_outp(info, UART_IER, 0x0f);      /* enable all intrs */
        }
        sti();
        /*
         * Next, clear the interrupt registers.
         */
        (void)serial_inp(info, UART_LSR);
        (void)serial_inp(info, UART_RX);
        (void)serial_inp(info, UART_IIR);
        (void)serial_inp(info, UART_MSR);
        
        timeout = jiffies+2;
        while (timeout >= jiffies) {
                if (rs_irq_triggered)
                        break;
        }
        /*
         * Now check to see if we got any business, and clean up.
         */
        cli();
        serial_outp(info, UART_IER, save_IER);
        serial_outp(info, UART_MCR, save_MCR);
        if (info->flags & ASYNC_FOURPORT)
                outb_p(save_ICP, ICP);
        sti();
        return(rs_irq_triggered);
}

/*
 * Calls get_auto_irq() multiple times, to make sure we don't get
 * faked out by random interrupts
 */
static int do_auto_irq(struct async_struct * info)
{
        unsigned                port = info->port;
        int                     irq_lines = 0;
        int                     irq_try_1 = 0, irq_try_2 = 0;
        int                     retries;
        unsigned long flags;

        if (!port)
                return 0;

        /* Turn on interrupts (they may be off) */
        save_flags(flags); sti();

        irq_lines = grab_all_interrupts(rs_wild_int_mask);
        
        for (retries = 0; retries < 5; retries++) {
                if (!irq_try_1)
                        irq_try_1 = get_auto_irq(info);
                if (!irq_try_2)
                        irq_try_2 = get_auto_irq(info);
                if (irq_try_1 && irq_try_2) {
                        if (irq_try_1 == irq_try_2)
                                break;
                        irq_try_1 = irq_try_2 = 0;
                }
        }
        restore_flags(flags);
        free_all_interrupts(irq_lines);
        return (irq_try_1 == irq_try_2) ? irq_try_1 : 0;
}

/*
 * This routine is called by rs_init() to initialize a specific serial
 * port.  It determines what type of UART ship this serial port is
 * using: 8250, 16450, 16550, 16550A.  The important question is
 * whether or not this UART is a 16550A or not, since this will
 * determine whether or not we can use its FIFO features or not.
 */
static void autoconfig(struct async_struct * info)
{
        unsigned char status1, status2, scratch, scratch2;
        unsigned port = info->port;
        unsigned long flags;

        info->type = PORT_UNKNOWN;
        
        if (!port)
                return;

        save_flags(flags); cli();
        
        /*
         * Do a simple existence test first; if we fail this, there's
         * no point trying anything else.
         */
        scratch = serial_inp(info, UART_IER);
        serial_outp(info, UART_IER, 0);
        scratch2 = serial_inp(info, UART_IER);
        serial_outp(info, UART_IER, scratch);
        if (scratch2) {
                restore_flags(flags);
                return;         /* We failed; there's nothing here */
        }

        /* 
         * Check to see if a UART is really there.  Certain broken
         * internal modems based on the Rockwell chipset fail this
         * test, because they apparently don't implement the loopback
         * test mode.  So this test is skipped on the COM 1 through
         * COM 4 ports.  This *should* be safe, since no board
         * manufactucturer would be stupid enough to design a board
         * that conflicts with COM 1-4 --- we hope!
         */
        if (!(info->flags & ASYNC_SKIP_TEST)) {
                scratch = serial_inp(info, UART_MCR);
                serial_outp(info, UART_MCR, UART_MCR_LOOP | scratch);
                scratch2 = serial_inp(info, UART_MSR);
                serial_outp(info, UART_MCR, UART_MCR_LOOP | 0x0A);
                status1 = serial_inp(info, UART_MSR) & 0xF0;
                serial_outp(info, UART_MCR, scratch);
                serial_outp(info, UART_MSR, scratch2);
                if (status1 != 0x90) {
                        restore_flags(flags);
                        return;
                }
        } 
        
        /*
         * If the AUTO_IRQ flag is set, try to do the automatic IRQ
         * detection.
         */
        if (info->flags & ASYNC_AUTO_IRQ)
                info->irq = do_auto_irq(info);
                
        serial_outp(info, UART_FCR, UART_FCR_ENABLE_FIFO);
        scratch = serial_in(info, UART_IIR) >> 6;
        info->xmit_fifo_size = 1;
        switch (scratch) {
                case 0:
                        info->type = PORT_16450;
                        break;
                case 1:
                        info->type = PORT_UNKNOWN;
                        break;
                case 2:
                        info->type = PORT_16550;
                        break;
                case 3:
                        info->type = PORT_16550A;
                        info->xmit_fifo_size = 16;
                        break;
        }
        if (info->type == PORT_16450) {
                scratch = serial_in(info, UART_SCR);
                serial_outp(info, UART_SCR, 0xa5);
                status1 = serial_in(info, UART_SCR);
                serial_outp(info, UART_SCR, 0x5a);
                status2 = serial_in(info, UART_SCR);
                serial_outp(info, UART_SCR, scratch);

                if ((status1 != 0xa5) || (status2 != 0x5a))
                        info->type = PORT_8250;
        }
        shutdown(info);
        restore_flags(flags);
}

/*
 * The serial driver boot-time initialization code!
 */
long rs_init(long kmem_start)
{
        int i;
        struct async_struct * info;
        
        memset(&rs_event, 0, sizeof(rs_event));
        bh_base[SERIAL_BH].routine = do_softint;
        timer_table[RS_TIMER].fn = rs_timer;
        timer_table[RS_TIMER].expires = 0;
        IRQ_active = 0;
#ifdef CONFIG_AUTO_IRQ
        rs_wild_int_mask = check_wild_interrupts(1);
#endif

        for (i = 0; i < 16; i++) {
                IRQ_ports[i] = 0;
                IRQ_timeout[i] = 0;
        }
        
        show_serial_version();
        for (i = 0, info = rs_table; i < NR_PORTS; i++,info++) {
                info->line = i;
                info->tty = 0;
                info->type = PORT_UNKNOWN;
                info->custom_divisor = 0;
                info->close_delay = 50;
                info->x_char = 0;
                info->event = 0;
                info->count = 0;
                info->blocked_open = 0;
                info->open_wait = 0;
                info->next_port = 0;
                info->prev_port = 0;
                if (info->irq == 2)
                        info->irq = 9;
                if (!(info->flags & ASYNC_BOOT_AUTOCONF))
                        continue;
                autoconfig(info);
                if (info->type == PORT_UNKNOWN)
                        continue;
                printk("tty%02d%s at 0x%04x (irq = %d)", info->line, 
                       (info->flags & ASYNC_FOURPORT) ? " FourPort" : "",
                       info->port, info->irq);
                switch (info->type) {
                        case PORT_8250:
                                printk(" is a 8250\n");
                                break;
                        case PORT_16450:
                                printk(" is a 16450\n");
                                break;
                        case PORT_16550:
                                printk(" is a 16550\n");
                                break;
                        case PORT_16550A:
                                printk(" is a 16550A\n");
                                break;
                        default:
                                printk("\n");
                                break;
                }
        }
        return kmem_start;
}


------------------------------


** FOR YOUR REFERENCE **

The service address, to which questions about the list itself and requests
to be added to or deleted from it should be directed, is:

    Internet: Linux-Admin-Request@NEWS-DIGESTS.MIT.EDU

You can send mail to the entire list (and comp.os.linux.admin) via:

    Internet: Linux-Admin@NEWS-DIGESTS.MIT.EDU

Linux may be obtained via one of these FTP sites:
    nic.funet.fi				pub/OS/Linux
    tsx-11.mit.edu				pub/linux
    sunsite.unc.edu				pub/Linux

End of Linux-Admin Digest
******************************
