These patches were made against Linux 2.1.14, but they should
apply cleanly against 2.1.13, and possibly 2.1.12.
- Ted
Patch generated: on Fri Dec 6 11:22:00 EST 1996 by tytso@lurch.mit.edu
against Linux version 2.1.14
===================================================================
RCS file: drivers/char/RCS/ChangeLog,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/ChangeLog
--- drivers/char/ChangeLog 1996/12/04 15:03:01 1.1
+++ drivers/char/ChangeLog 1996/12/04 15:03:04
@@ -1,3 +1,57 @@
+Wed Dec 4 07:51:52 1996 Theodre Ts'o <tytso@localhost.mit.edu>
+
+ * serial.c (change_speed): Use save_flags(); cli() and
+ restore_flags() in order to ensure we don't accidentally
+ turn on interrupts when starting up the port.
+ (startup): Move the insertion of serial structure into the
+ IRQ chain earlier into the startup processing. Interrupts
+ should be off this whole time, but we eventually will want
+ to reduce this window.
+
+Thu Nov 21 10:05:22 1996 Theodre Ts'o <tytso@localhost.mit.edu>
+
+ * tty_ioctl.c (tty_wait_until_sent): Always check the driver
+ wait_until_ready routine, even if there are no characters
+ in the xmit buffer. (There may be charactes in the device
+ FIFO.)
+ (n_tty_ioctl): Add new flag tty->flow_stopped which
+ indicates whether the tty is stopped due to a request by
+ the TCXONC ioctl (used by tcflow). If so, don't let an
+ incoming XOFF character restart the tty. The tty can only
+ be restarted by another TCXONC request.
+
+ * tty_io.c (start_tty): Don't allow the tty to be restarted if
+ tty->flow_stopped is true.
+
+ * n_tty.c (n_tty_receive_char): If tty->flow_stopped is true, and
+ IXANY is set, don't eat a character trying to restart the
+ tty.
+
+ * serial.c (startup): Remove need for MCR_noint from the
+ async_struct structure. Only turn on DTR and RTS if the
+ baud rate is not zero.
+ (change_speed): More accurately calculate the timeout
+ value based on the word size. Move responsibility of
+ hangup when speed becomes B0 to rs_set_termios()
+ (set_serial_info): When changing the UART type set the
+ current xmit_fifo_size as well as the permanent
+ xmit_fifo_size.
+ (rs_ioctl): Fix TCSBRK (used by tcdrain) and TCSBRKP
+ ioctls to return EINTR if interrupted by a signal.
+ (rs_set_termios): If the baud rate changes to or from B0,
+ this function is now responsible for setting or clearing
+ DTR and RTS. DTR and RTS are only be changed on the
+ transition to or from the B0 state.
+ (rs_close): Wait for the characters to drain based on
+ info->timeout. At low baud rates (50bps), it may take a
+ long time for the FIFO to completely drain out!
+ (rs_wait_until_sent): Fixed timeout handling. Now
+ releases control to the scheduler, but checks frequently
+ enough so that the function is sensitive enough to pass
+ the timing requirements of the NIST-PCTS.
+ (block_til_ready): When opening the device, don't turn on
+ DTR and RTS if the baud rate is B0.
+
Thu Nov 14 00:06:09 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
* serial.c (autoconfig): Fix autoconfiguration problems;
===================================================================
RCS file: include/linux/RCS/serial.h,v
retrieving revision 1.1
diff -u -r1.1 include/linux/serial.h
--- include/linux/serial.h 1996/12/04 15:03:01 1.1
+++ include/linux/serial.h 1996/12/04 15:03:04
@@ -184,7 +184,6 @@
unsigned short closing_wait2;
int IER; /* Interrupt Enable Register */
int MCR; /* Modem control register */
- int MCR_noint; /* MCR with interrupts off */
unsigned long event;
unsigned long last_active;
int line;
===================================================================
RCS file: include/linux/RCS/tty.h,v
retrieving revision 1.1
diff -u -r1.1 include/linux/tty.h
--- include/linux/tty.h 1996/12/04 15:03:01 1.1
+++ include/linux/tty.h 1996/12/04 15:47:42
@@ -213,7 +213,7 @@
unsigned long flags;
int count;
struct winsize winsize;
- unsigned char stopped:1, hw_stopped:1, packet:1;
+ unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1;
unsigned char ctrl_status;
struct tty_struct *link;
===================================================================
RCS file: drivers/char/RCS/serial.c,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/serial.c
--- drivers/char/serial.c 1996/12/04 15:03:01 1.1
+++ drivers/char/serial.c 1996/12/04 15:03:05
@@ -50,7 +50,7 @@
#include <asm/bitops.h>
static char *serial_name = "Serial driver";
-static char *serial_version = "4.21";
+static char *serial_version = "4.22";
DECLARE_TASK_QUEUE(tq_serial);
@@ -83,6 +83,7 @@
#undef SERIAL_DEBUG_INTR
#undef SERIAL_DEBUG_OPEN
#undef SERIAL_DEBUG_FLOW
+#undef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
#define RS_STROBE_TIME (10*HZ)
#define RS_ISR_PASS_LIMIT 256
@@ -1040,6 +1041,16 @@
}
/*
+ * Insert serial port into IRQ chain.
+ */
+ info->prev_port = 0;
+ info->next_port = IRQ_ports[state->irq];
+ if (info->next_port)
+ info->next_port->prev_port = info;
+ IRQ_ports[state->irq] = info;
+ figure_IRQ_timeout(state->irq);
+
+ /*
* Clear the interrupt registers.
*/
/* (void) serial_inp(info, UART_LSR); */ /* (see above) */
@@ -1051,19 +1062,23 @@
* Now, initialize the UART
*/
serial_outp(info, UART_LCR, UART_LCR_WLEN8); /* reset DLAB */
- if (info->flags & ASYNC_FOURPORT) {
+
+ info->MCR = 0;
+ if (info->tty->termios->c_cflag & CBAUD)
info->MCR = UART_MCR_DTR | UART_MCR_RTS;
- info->MCR_noint = UART_MCR_DTR | UART_MCR_OUT1;
+ if (info->flags & ASYNC_FOURPORT) {
+ if (state->irq == 0)
+ info->MCR |= UART_MCR_OUT1;
} else {
- info->MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2;
- info->MCR_noint = UART_MCR_DTR | UART_MCR_RTS;
+ if (state->irq != 0)
+ info->MCR |= UART_MCR_OUT2;
}
#if defined(__alpha__) && !defined(CONFIG_PCI)
+ /*
+ * DEC did something gratutiously wrong....
+ */
info->MCR |= UART_MCR_OUT1 | UART_MCR_OUT2;
- info->MCR_noint |= UART_MCR_OUT1 | UART_MCR_OUT2;
#endif
- if (state->irq == 0)
- info->MCR = info->MCR_noint;
serial_outp(info, UART_MCR, info->MCR);
/*
@@ -1092,16 +1107,6 @@
info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
/*
- * Insert serial port into IRQ chain.
- */
- info->prev_port = 0;
- info->next_port = IRQ_ports[state->irq];
- if (info->next_port)
- info->next_port->prev_port = info;
- IRQ_ports[state->irq] = info;
- figure_IRQ_timeout(state->irq);
-
- /*
* Set up serial timers...
*/
timer_table[RS_TIMER].expires = jiffies + 2*HZ/100;
@@ -1183,13 +1188,19 @@
if (info->flags & ASYNC_FOURPORT) {
/* reset interrupts on the AST Fourport board */
(void) inb((info->port & 0xFE0) | 0x01F);
- }
+ info->MCR |= UART_MCR_OUT1;
+ } else
+ info->MCR &= ~UART_MCR_OUT2;
+#if defined(__alpha__) && !defined(CONFIG_PCI)
+ /*
+ * DEC did something gratutiously wrong....
+ */
+ info->MCR |= UART_MCR_OUT1 | UART_MCR_OUT2;
+#endif
- if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) {
+ if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
- info->MCR_noint &= ~(UART_MCR_DTR|UART_MCR_RTS);
- }
- serial_outp(info, UART_MCR, info->MCR_noint);
+ serial_outp(info, UART_MCR, info->MCR);
/* disable FIFO's */
serial_outp(info, UART_FCR, (UART_FCR_CLEAR_RCVR |
@@ -1223,13 +1234,36 @@
unsigned short port;
int quot = 0, baud_base;
unsigned cflag, cval, fcr = 0;
- int i;
+ int i, bits;
+ unsigned long flags;
if (!info->tty || !info->tty->termios)
return;
cflag = info->tty->termios->c_cflag;
if (!(port = info->port))
return;
+
+ /* byte size and parity */
+ switch (cflag & CSIZE) {
+ case CS5: cval = 0x00; bits = 7; break;
+ case CS6: cval = 0x01; bits = 8; break;
+ case CS7: cval = 0x02; bits = 9; break;
+ case CS8: cval = 0x03; bits = 10; break;
+ /* Never happens, but GCC is too dumb to figure it out */
+ default: cval = 0x00; bits = 7; break;
+ }
+ if (cflag & CSTOPB) {
+ cval |= 0x04;
+ bits++;
+ }
+ if (cflag & PARENB) {
+ cval |= UART_LCR_PARITY;
+ bits++;
+ }
+ if (!(cflag & PARODD))
+ cval |= UART_LCR_EPAR;
+
+ /* Determine divisor based on baud rate */
i = cflag & CBAUD;
if (i & CBAUDEX) {
i &= ~CBAUDEX;
@@ -1251,42 +1285,20 @@
quot = info->state->custom_divisor;
}
baud_base = info->state->baud_base;
- if (quot) {
- info->timeout = ((info->xmit_fifo_size*HZ*15*quot) /
- baud_base) + 2;
- } else if (baud_table[i] == 134) {
- quot = (2*baud_base / 269);
- info->timeout = (info->xmit_fifo_size*HZ*30/269) + 2;
- } else if (baud_table[i]) {
- quot = baud_base / baud_table[i];
- info->timeout = (info->xmit_fifo_size*HZ*15/baud_table[i]) + 2;
- } else {
- quot = 0;
- info->timeout = 0;
- }
if (!quot) {
- info->MCR &= ~UART_MCR_DTR;
- info->MCR_noint &= ~UART_MCR_DTR;
- cli();
- serial_out(info, UART_MCR, info->MCR);
- sti();
- return;
- }
- /* byte size and parity */
- switch (cflag & CSIZE) {
- case CS5: cval = 0x00; break;
- case CS6: cval = 0x01; break;
- case CS7: cval = 0x02; break;
- case CS8: cval = 0x03; break;
- default: cval = 0x00; break; /* too keep GCC shut... */
+ if (baud_table[i] == 134)
+ /* Special case since 134 is really 134.5 */
+ quot = (2*baud_base / 269);
+ else if (baud_table[i])
+ quot = baud_base / baud_table[i];
+ /* If the quotient is ever zero, default to 9600 bps */
+ if (!quot)
+ quot = baud_base / 9600;
}
- if (cflag & CSTOPB) {
- cval |= 0x04;
- }
- if (cflag & PARENB)
- cval |= UART_LCR_PARITY;
- if (!(cflag & PARODD))
- cval |= UART_LCR_EPAR;
+ info->timeout = ((info->xmit_fifo_size*HZ*bits*quot) / baud_base);
+ info->timeout += HZ/50; /* Add .02 seconds of slop */
+
+ /* Set up FIFO's */
if (uart_config[info->state->type].flags & UART_USE_FIFO) {
if ((info->state->baud_base / quot) < 2400)
fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
@@ -1344,7 +1356,7 @@
*/
if ((cflag & CREAD) == 0)
info->ignore_status_mask |= UART_LSR_DR;
- cli();
+ save_flags(flags); cli();
if (uart_config[info->state->type].flags & UART_STARTECH) {
serial_outp(info, UART_LCR, 0xBF);
serial_outp(info, UART_EFR,
@@ -1357,7 +1369,7 @@
serial_outp(info, UART_FCR, fcr); /* set fcr */
serial_outp(info, UART_LCR, cval); /* reset DLAB */
serial_outp(info, UART_FCR, fcr); /* set fcr */
- sti();
+ restore_flags(flags);
}
static void rs_put_char(struct tty_struct *tty, unsigned char ch)
@@ -1529,10 +1541,9 @@
if (I_IXOFF(tty))
rs_send_xchar(tty, STOP_CHAR(tty));
- if (tty->termios->c_cflag & CRTSCTS) {
+ if (tty->termios->c_cflag & CRTSCTS)
info->MCR &= ~UART_MCR_RTS;
- info->MCR_noint &= ~UART_MCR_RTS;
- }
+
cli();
serial_out(info, UART_MCR, info->MCR);
sti();
@@ -1557,10 +1568,8 @@
else
rs_send_xchar(tty, START_CHAR(tty));
}
- if (tty->termios->c_cflag & CRTSCTS) {
+ if (tty->termios->c_cflag & CRTSCTS)
info->MCR |= UART_MCR_RTS;
- info->MCR_noint |= UART_MCR_RTS;
- }
cli();
serial_out(info, UART_MCR, info->MCR);
sti();
@@ -1685,7 +1694,7 @@
if (!state->port || !state->type)
return 0;
if (state->type != old_state.type)
- state->xmit_fifo_size =
+ info->xmit_fifo_size = state->xmit_fifo_size =
uart_config[state->type].dfl_xmit_fifo_size;
if (state->flags & ASYNC_INITIALIZED) {
if (((old_state.flags & ASYNC_SPD_MASK) !=
@@ -1752,33 +1761,21 @@
return error;
switch (cmd) {
case TIOCMBIS:
- if (arg & TIOCM_RTS) {
+ if (arg & TIOCM_RTS)
info->MCR |= UART_MCR_RTS;
- info->MCR_noint |= UART_MCR_RTS;
- }
- if (arg & TIOCM_DTR) {
+ if (arg & TIOCM_DTR)
info->MCR |= UART_MCR_DTR;
- info->MCR_noint |= UART_MCR_DTR;
- }
break;
case TIOCMBIC:
- if (arg & TIOCM_RTS) {
+ if (arg & TIOCM_RTS)
info->MCR &= ~UART_MCR_RTS;
- info->MCR_noint &= ~UART_MCR_RTS;
- }
- if (arg & TIOCM_DTR) {
+ if (arg & TIOCM_DTR)
info->MCR &= ~UART_MCR_DTR;
- info->MCR_noint &= ~UART_MCR_DTR;
- }
break;
case TIOCMSET:
info->MCR = ((info->MCR & ~(UART_MCR_RTS | UART_MCR_DTR))
| ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0)
| ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0));
- info->MCR_noint = ((info->MCR_noint
- & ~(UART_MCR_RTS | UART_MCR_DTR))
- | ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0)
- | ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0));
break;
default:
return -EINVAL;
@@ -1821,11 +1818,17 @@
return;
current->state = TASK_INTERRUPTIBLE;
current->timeout = jiffies + duration;
+#ifdef SERIAL_DEBUG_SEND_BREAK
+ printk("rs_send_break(%d) jiff=%lu...", duration, jiffies);
+#endif
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();
+#ifdef SERIAL_DEBUG_SEND_BREAK
+ printk("done jiffies=%lu\n", jiffies);
+#endif
}
/*
@@ -2012,15 +2015,24 @@
if (retval)
return retval;
tty_wait_until_sent(tty, 0);
- if (!arg)
+ if (current->signal & ~current->blocked)
+ return -EINTR;
+ if (!arg) {
send_break(info, HZ/4); /* 1/4 second */
+ if (current->signal & ~current->blocked)
+ return -EINTR;
+ }
return 0;
case TCSBRKP: /* support for POSIX tcsendbreak() */
retval = tty_check_change(tty);
if (retval)
return retval;
tty_wait_until_sent(tty, 0);
+ if (current->signal & ~current->blocked)
+ return -EINTR;
send_break(info, arg ? arg*(HZ/10) : HZ/4);
+ if (current->signal & ~current->blocked)
+ return -EINTR;
return 0;
case TIOCGSOFTCAR:
return put_user(C_CLOCAL(tty) ? 1 : 0, (int *) arg);
@@ -2179,14 +2191,29 @@
change_speed(info);
+ /* Handle transition to B0 status */
+ if ((old_termios->c_cflag & CBAUD) &&
+ !(tty->termios->c_cflag & CBAUD)) {
+ info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
+ cli();
+ serial_out(info, UART_MCR, info->MCR);
+ sti();
+ }
+
+ /* Handle transition away from B0 status */
if (!(old_termios->c_cflag & CBAUD) &&
(tty->termios->c_cflag & CBAUD)) {
info->MCR |= UART_MCR_DTR;
- info->MCR_noint |= UART_MCR_DTR;
+ if (!tty->hw_stopped ||
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ info->MCR |= UART_MCR_RTS;
+ }
cli();
serial_out(info, UART_MCR, info->MCR);
sti();
}
+
+ /* Handle turning off CRTSCTS */
if ((old_termios->c_cflag & CRTSCTS) &&
!(tty->termios->c_cflag & CRTSCTS)) {
tty->hw_stopped = 0;
@@ -2293,7 +2320,7 @@
* has completely drained; this is especially
* important if there is a transmit FIFO!
*/
- rs_wait_until_sent(tty, HZ);
+ rs_wait_until_sent(tty, info->timeout);
}
shutdown(info);
if (tty->driver.flush_buffer)
@@ -2324,25 +2351,48 @@
static void rs_wait_until_sent(struct tty_struct *tty, int timeout)
{
struct async_struct * info = (struct async_struct *)tty->driver_data;
- unsigned long orig_jiffies;
+ unsigned long orig_jiffies, char_time;
+ int lsr;
if (serial_paranoia_check(info, tty->device, "rs_wait_until_sent"))
return;
orig_jiffies = jiffies;
- current->state = TASK_INTERRUPTIBLE;
- current->counter = 0; /* make us low-priority */
- while (!(serial_inp(info, UART_LSR) & UART_LSR_TEMT)) {
- current->timeout = jiffies + info->timeout;
+ /*
+ * Set the check interval to be 1/5 of the estimated time to
+ * send a single character, and make it at least 1. The check
+ * interval should also be less than the timeout.
+ *
+ * Note: we have to use pretty tight timings here to satisfy
+ * the NIST-PCTS.
+ */
+ char_time = (info->timeout - HZ/50) / info->xmit_fifo_size;
+ char_time = char_time / 5;
+ if (char_time == 0)
+ char_time = 1;
+ if (timeout)
+ char_time = MIN(char_time, timeout);
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+ printk("In rs_wait_until_sent(%d) check=%lu...", timeout, char_time);
+ printk("jiff=%lu...", jiffies);
+#endif
+ while (!((lsr = serial_inp(info, UART_LSR)) & UART_LSR_TEMT)) {
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+ printk("lsr = %d (jiff=%lu)...", lsr, jiffies);
+#endif
+ current->state = TASK_INTERRUPTIBLE;
+ current->counter = 0; /* make us low-priority */
+ current->timeout = jiffies + char_time;
schedule();
if (current->signal & ~current->blocked)
break;
- if (timeout && ((orig_jiffies + timeout) > jiffies))
- break;
- if (jiffies > timeout)
+ if (timeout && ((orig_jiffies + timeout) < jiffies))
break;
}
current->state = TASK_RUNNING;
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+ printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies);
+#endif
}
/*
@@ -2457,7 +2507,8 @@
info->blocked_open++;
while (1) {
cli();
- if (!(info->flags & ASYNC_CALLOUT_ACTIVE))
+ if (!(info->flags & ASYNC_CALLOUT_ACTIVE) &&
+ (tty->termios->c_cflag & CBAUD))
serial_out(info, UART_MCR,
serial_inp(info, UART_MCR) |
(UART_MCR_DTR | UART_MCR_RTS));
===================================================================
RCS file: drivers/char/RCS/tty_ioctl.c,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/tty_ioctl.c
--- drivers/char/tty_ioctl.c 1996/12/04 15:03:01 1.1
+++ drivers/char/tty_ioctl.c 1996/12/04 15:03:05
@@ -47,8 +47,7 @@
#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
printk("%s wait until sent...\n", tty_name(tty));
#endif
- if (!tty->driver.chars_in_buffer ||
- !tty->driver.chars_in_buffer(tty))
+ if (!tty->driver.chars_in_buffer)
return;
add_wait_queue(&tty->write_wait, &wait);
current->counter = 0; /* make us low-priority */
@@ -462,10 +461,16 @@
return retval;
switch (arg) {
case TCOOFF:
- stop_tty(tty);
+ if (!tty->flow_stopped) {
+ tty->flow_stopped = 1;
+ stop_tty(tty);
+ }
break;
case TCOON:
- start_tty(tty);
+ if (tty->flow_stopped) {
+ tty->flow_stopped = 0;
+ start_tty(tty);
+ }
break;
case TCIOFF:
if (STOP_CHAR(tty) != __DISABLED_CHAR)
===================================================================
RCS file: drivers/char/RCS/tty_io.c,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/tty_io.c
--- drivers/char/tty_io.c 1996/12/04 15:03:01 1.1
+++ drivers/char/tty_io.c 1996/12/04 15:03:05
@@ -705,7 +705,7 @@
void start_tty(struct tty_struct *tty)
{
- if (!tty->stopped)
+ if (!tty->stopped || tty->flow_stopped)
return;
tty->stopped = 0;
if (tty->link && tty->link->packet) {
===================================================================
RCS file: drivers/char/RCS/n_tty.c,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/n_tty.c
--- drivers/char/n_tty.c 1996/12/04 15:03:02 1.1
+++ drivers/char/n_tty.c 1996/12/04 15:03:05
@@ -355,7 +355,8 @@
return;
}
- if (tty->stopped && I_IXON(tty) && I_IXANY(tty)) {
+ if (tty->stopped && !tty->flow_stopped &&
+ I_IXON(tty) && I_IXANY(tty)) {
start_tty(tty);
return;
}