lkml.org 
[lkml]   [1996]   [Dec]   [6]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
SubjectUpdate to the serial driver

Linus,
Enclosed please find some patches to the serial driver. With
these patches, the Linux serial driver passes nearly all of the POSIX
conformance tests. There is still one test which is failing, which
seems to be a some kind strange timing/scheduling problem (Linux does
the right thing, but the PCTS test failes --- unless you try single
stepping it or debugging it. :-) I'll keep working on it, but in the
mean time I want to send you the patches that I have.

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;
}
\
 
 \ /
  Last update: 2005-03-22 13:38    [W:0.226 / U:0.136 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site