patch: serial 16550C autoflow

Frank Gockel (gockel@sent13.uni-duisburg.de)
Wed, 23 Sep 1998 12:17:26 +0200 (MET DST)


Hi,

I'm not sure whether this has already been implemented, but at least I
haven't found a patch for the kernel on the net. This patch promises
serial data transmission without FIFO overrun/underrun errors resulting
in data loss, confused modems, hanging modem software etc. given that
you have one of those 16550C UART chips (quite common in today's serial
cards but not yet built into the main boards). It works even under high
system load.

This is the third release of my 16550C autoflow diff for 2.0.xx kernels.
It was created against 2.0.35 and may not work for other kernels. It is
expected to fail under latest 2.1.xx kernels (not tested).

What it does?

First take a look at the chipset release. The 16550 UART series has a number
of subreleases:

16550 First version, buggy. Very rarely used. (Replace!)
16550A Most common version. This chip is today's standard.
16550B Enhanced version.
16550C More enhanced version with hardware autoflow.

Linux detects the first buggy 16550 release and enables a workaround for
the bug (by disabling its FIFO), so it's just treated as and as bad as
an old 16450. (Be warned, some UART test software reports 16550 though
a 16550A is present. Trust Linux.)

The A, B and C subreleases are treated as 16550A by the standard kernel.

This patch now enables detection of the 16550C and uses one of its
advanced feature: hardware autoflow. This feature handles the CTS and RTS
automatically (i.e. by hardware in the chip) depending on FIFO state.
This means, if the FIFO gets full the chip itself stops the data transfer
via RTS and CTS lines. No software interaction is necessary for that.
On a 16550A, at least one interrupt has to be served and the interrupt
handler has to handle CTS and RTS changes which may be too slow in some
cases and result in data loss. Those situations do not occur when a 16550C
chip handles flow control automatically by hardware. The hardware autoflow
control is bidirectional i.e. works both for incoming and outgoing data.

The 16550C is used in serial cards that have a jumper to double the clock
or even multiply it with 4. This results in transfer rates of 230400 and
460800 Baud. At least 230400 Baud works very reliable with this patch (it
has been under test for some months with an ISDN modem). Well, most analog
modems handle only up to 115200 Baud.

Though some people claim that just setting the autoflow enable bit in the
UART config registers would be enough I found that this leads to sudden
lockups or pauses in data transmission. I know at least one modem that
goes even completely banana in that case (it's really the modem, an el cheapo
clone). So I hacked the serial driver a bit in order to prevent the problems.

Odd things that may happen:

WARNING: Some (all?) BIOSes are not aware of 16550C and do not reset the
autoflow enable bit when rebooting. This means that you may have to power
cycle your machine if you want to boot into other operating systems and want
to use the UART there. Linux kernels without this patch also do not reset
the bit. If autoflow is enabled but the serial driver is not aware of
it, sudden lockups or pauses in data transmission may occur.

Frank

--- linux/drivers/char/serial.c.orig Thu Jun 11 14:23:20 1998
+++ linux/drivers/char/serial.c Sun Sep 13 15:34:08 1998
@@ -19,6 +19,8 @@
* flags INPCK, BRKINT, PARMRK, IGNPAR and IGNBRK.
* Bernd Anhäupl 05/17/96.
*
+ * 09/97: autoflow support for 16550C added by Frank Gockel
+ *
* This module exports the following rs232 io functions:
*
* int rs_init(void);
@@ -68,16 +70,45 @@
*
* CONFIG_HUB6
* Enables support for the venerable Bell Technologies
- * HUB6 card.
+ * HUB6 card. (This is set or unset by 'make config'.)
*
* SERIAL_PARANOIA_CHECK
* Check the magic number for the async_structure where
* ever possible.
+ *
+ * SERIAL_USE_AUTOFLOW
+ * Use RTS/CTS autoflow on chipsets that support it. Autoflow
+ * means two things. First, the transmitter circuitry stops
+ * sending the fifo content when CTS drops and resumes
+ * automatically when CTS is active again (Auto-CTS). Second,
+ * it drops RTS when the receiver fifo trigger level is reached
+ * and rises the RTS line again when there's enough space
+ * (Auto-RTS). Thus the receiver fifo cannot overrun even if
+ * software driver is too slow in dropping RTS (due to heavy
+ * system load or much interrupt activity).
+ * *** Currently only implemented for the 16550C. The 16650
+ * can do autoflow too, but it uses different registers.
+ * However, current 16650 support in this driver lacks fifo
+ * support so it doesn't make much sense today :(
+ *
+ * SERIAL_AUTOFLOW_MAX_FIFO
+ * Set fifo threshold to maximum when using autoflow. This
+ * improves performance by reducing interrupt load, but it
+ * requires Really Well implemented hard/software on the other
+ * end of the serial connection. Enable this option only if you
+ * know the device on the other end immediately stops sending
+ * data when RTS drops. I.e. if the other end is a 16550A UART
+ * then don't enable this option (the 16550A always sends its
+ * fifo until it's empty), but if it's a 16550C with autoflow
+ * also enabled then it's safe. Most modems are usually safe in
+ * this manner.
*/

#define SERIAL_PARANOIA_CHECK
#define CONFIG_SERIAL_NOPAUSE_IO
#define SERIAL_DO_RESTART
+#define SERIAL_USE_AUTOFLOW
+/*#define SERIAL_AUTOFLOW_MAX_FIFO*/

#undef SERIAL_DEBUG_INTR
#undef SERIAL_DEBUG_OPEN
@@ -252,7 +283,7 @@
*/
static int baud_table[] = {
0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
- 9600, 19200, 38400, 57600, 115200, 0 };
+ 9600, 19200, 38400, 57600, 115200, 230400, 0 };

static inline unsigned int serial_in(struct async_struct *info, int offset)
{
@@ -526,9 +557,16 @@
#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))
printk("CTS tx stop...");
#endif
+ /* stop the device if not in autoflow mode */
+ if(!(info->flags&ASYNC_AUTOFLOW))
+ {
info->tty->hw_stopped = 1;
info->IER &= ~UART_IER_THRI;
serial_out(info, UART_IER, info->IER);
+ }
+#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))
+ else printk("autoflow cont'd...");
+#endif
}
}
}
@@ -949,7 +987,7 @@
serial_outp(info, UART_FCR, (UART_FCR_CLEAR_RCVR |
UART_FCR_CLEAR_XMIT));
info->xmit_fifo_size = 1; /* disabled for now */
- } else if (info->type == PORT_16550A) {
+ } else if (info->type == PORT_16550A || info->type == PORT_16550C) {
serial_outp(info, UART_FCR, (UART_FCR_CLEAR_RCVR |
UART_FCR_CLEAR_XMIT));
info->xmit_fifo_size = 16;
@@ -1018,6 +1056,10 @@
info->MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2;
info->MCR_noint = UART_MCR_DTR | UART_MCR_RTS;
}
+ if (info->port == PORT_16550C && info->flags & ASYNC_AUTOFLOW)
+ { info->MCR |= UART_MCR_AFE;
+ info->MCR_noint |= UART_MCR_AFE;
+ }
#if defined(__alpha__) && !defined(CONFIG_PCI)
info->MCR |= UART_MCR_OUT1 | UART_MCR_OUT2;
info->MCR_noint |= UART_MCR_OUT1 | UART_MCR_OUT2;
@@ -1180,7 +1222,7 @@
i = cflag & CBAUD;
if (i & CBAUDEX) {
i &= ~CBAUDEX;
- if (i < 1 || i > 2)
+ if (i < 1 || i > 3)
info->tty->termios->c_cflag &= ~CBAUDEX;
else
i += 15;
@@ -1235,7 +1277,7 @@
cval |= UART_LCR_PARITY;
if (!(cflag & PARODD))
cval |= UART_LCR_EPAR;
- if (info->type == PORT_16550A) {
+ if (info->type == PORT_16550A || info->type == PORT_16550C) {
if ((info->baud_base / quot) < 2400)
fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
else
@@ -1260,8 +1302,29 @@
if (cflag & CRTSCTS) {
info->flags |= ASYNC_CTS_FLOW;
info->IER |= UART_IER_MSI;
- } else
+#ifdef SERIAL_USE_AUTOFLOW
+ if(info->type == PORT_16550C)
+ { info->flags |= ASYNC_AUTOFLOW;
+ info->MCR |= UART_MCR_AFE;
+ info->MCR_noint |= UART_MCR_AFE;
+ cli();
+ serial_out(info, UART_MCR, info->MCR);
+ sti();
+ /*printk("autoflow enabled\n");*/
+#ifdef SERIAL_AUTOFLOW_MAX_FIFO
+ if(fcr==(UART_FCR_ENABLE_FIFO|UART_FCR_TRIGGER_8))
+ { /* we use trigger 14 to reduce interrupts */
+ fcr=UART_FCR_ENABLE_FIFO|UART_FCR_TRIGGER_14;
+ /*printk("fifo trigger set to 14\n");*/
+ }
+#endif
+ }
+ else info->flags &= ~ASYNC_AUTOFLOW;
+#endif
+ } else {
info->flags &= ~ASYNC_CTS_FLOW;
+ info->flags &= ~ASYNC_AUTOFLOW;
+ }
if (cflag & CLOCAL)
info->flags &= ~ASYNC_CHECK_CD;
else {
@@ -1365,7 +1428,7 @@

if (!tty || !info->xmit_buf || !tmp_buf)
return 0;
-
+
if (from_user)
down(&tmp_buf_sem);
save_flags(flags);
@@ -2482,6 +2545,10 @@
printk(" HUB-6");
#define SERIAL_OPT
#endif
+#ifdef SERIAL_USE_AUTOFLOW
+ printk(" 16550C-autoflow");
+#define SERIAL_OPT
+#endif
#ifdef SERIAL_OPT
printk(" enabled\n");
#else
@@ -2676,6 +2743,16 @@
} else {
info->type = PORT_16550A;
info->xmit_fifo_size = 16;
+ /* now check for 16550C */
+ { int h;
+ h=serial_in(info, UART_MCR)&0x1f;
+ serial_out(info, UART_MCR, h|UART_MCR_AFE);
+ if(serial_in(info, UART_MCR)==(h|UART_MCR_AFE))
+ { /*printk("16550C detected\n");*/
+ info->type=PORT_16550C;
+ }
+ serial_out(info, UART_MCR,h);
+ }
}
serial_outp(info, UART_LCR, scratch2);
break;
@@ -2847,6 +2924,9 @@
case PORT_16650:
printk(" is a 16650\n");
break;
+ case PORT_16550C:
+ printk(" is a 16550C\n");
+ break;
default:
printk("\n");
break;
@@ -2909,6 +2989,8 @@
printk(" is a 16550\n"); break;
case PORT_16550A:
printk(" is a 16550A\n"); break;
+ case PORT_16550C:
+ printk(" is a 16550C\n"); break;
default:
printk("\n"); break;
}
--- linux/include/linux/serial.h.orig Thu Sep 11 11:46:26 1997
+++ linux/include/linux/serial.h Thu Sep 11 13:37:46 1997
@@ -44,7 +44,8 @@
#define PORT_16550A 4
#define PORT_CIRRUS 5
#define PORT_16650 6
-#define PORT_MAX 6
+#define PORT_16550C 7
+#define PORT_MAX 7

/*
* Definitions for async_struct (and serial_struct) flags field
@@ -80,6 +81,7 @@
#define ASYNC_CTS_FLOW 0x04000000 /* Do CTS flow control */
#define ASYNC_CHECK_CD 0x02000000 /* i.e., CLOCAL */
#define ASYNC_SHARE_IRQ 0x01000000 /* for multifunction cards */
+#define ASYNC_AUTOFLOW 0x00800000 /* use cts/rts autoflow on 16550C */

/*
* Multiport serial configuration structure --- external structure
--- linux/include/linux/serial_reg.h.orig Thu Sep 11 13:01:04 1997
+++ linux/include/linux/serial_reg.h Thu Sep 11 13:01:57 1997
@@ -102,6 +102,7 @@
/*
* These are the definitions for the Modem Control Register
*/
+#define UART_MCR_AFE 0x20 /* Auto Flow Enable (only 16550C) */
#define UART_MCR_LOOP 0x10 /* Enable loopback test mode */
#define UART_MCR_OUT2 0x08 /* Out2 complement */
#define UART_MCR_OUT1 0x04 /* Out1 complement */

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/