Hi, Linux community! Some time ago I had to add a proprietary multiport card to my Linux box, and found a small incompatibility with the standard Linux serial driver. The driver confuses the NEC NS1655x-family UARTs with Startch ST16550, and decide not to use the FIFO, although the NEC UARTs fully support it. In addition, the identifying algorithm is able to mess up the state of NEC16552 Dual UART. The patch in attachment solved my problem. I generated and tested it only on my RedHat 6.2 Linux box with kernel 2.2.14. The patch adds an item in the kernel configuration menu ("CONFIG_NEC_DUART_SUPPORT"). Although it should be harmless, I suggest to use it only if really needed. Bye P.S. Please answer at my personal address, because I am not subscribed to the mailing list. -- -- Giuseppe Guerrini <[EMAIL PROTECTED]> ------------------------------------------------------------ L'ordine non e` che una configurazione particolare del caos.
Sun Dec 10 23:25:24 CET 2000 The NEC-DUART patch corrects a bug in Linux-2.2.14' serial driver (file linux/drivers/char/serial.c). The chipset probe algorithm doesn't identify as full-compatible 16550A UART the chips of the NEC NS1655x family: the FIFO is not enabled, although present and fully working. In addition, with the DUAL UART NS16552A, the chip state is messed up. Although harmful, the patch should be applied only if really needed. Use it only if you are sure that you are using a NEC NS1655x UART and are experiencing loss of characters. I have developed and tested my patch on Linux 2.2.14. I can't guaratee that it works well with other kernels. The patch must be applied as follows: (I assume that there is a complete kernel source tree in /usr/src/linux-2.2.14) su cd /usr/src patch -p0 < patch-NecDuart A new configuration item will be added in "character devices" section: Bugfix for NEC UARTs (NS16C55x) Enable it, save the new configuration and rebuild the kernel. Enjoy, Giuseppe Guerrini <[EMAIL PROTECTED]>
diff -Naur linux-2.2.14/Documentation/Configure.help linux-2.2.14-NecDuart/Documentation/Configure.help --- linux-2.2.14/Documentation/Configure.help Tue Apr 25 16:21:15 2000 +++ linux-2.2.14-NecDuart/Documentation/Configure.help Sun Dec 10 13:39:52 2000 @@ -1343,6 +1343,20 @@ Most people can say N here. +Bugfix for NEC NS16C55x UARTs +CONFIG_NEC_DUART_SUPPORT + Say Y here if you have a NEC NS1655x-family UART (NS16550Ax, + NS16C552x), and are experiencing problems (characters loss). + The standard serial driver is used to confuse the NEC UARTs + with Startech UARTs without FIFO support (NEC UARTs have + full 16550A FIFO). Use the "setserial" command to know if the + kernel has detected the wrong chip model. + + You must say Y also if you are using the SER2 or IOSPC64 cards + produced by CNi (www.cnisrl.it). + + If unsure, say N. + Extended dumb serial driver options CONFIG_SERIAL_EXTENDED If you wish to use any non-standard features of the standard "dumb" diff -Naur linux-2.2.14/drivers/char/Config.in linux-2.2.14-NecDuart/drivers/char/Config.in --- linux-2.2.14/drivers/char/Config.in Tue Apr 25 16:21:14 2000 +++ linux-2.2.14-NecDuart/drivers/char/Config.in Sun Dec 10 13:35:29 2000 @@ -10,6 +10,7 @@ fi tristate 'Standard/generic (dumb) serial support' CONFIG_SERIAL if [ "$CONFIG_SERIAL" = "y" ]; then + bool ' Bugfix for NEC UARTs (NS16C55x)' CONFIG_NEC_DUART_SUPPORT bool ' Support for console on serial port' CONFIG_SERIAL_CONSOLE fi bool 'Extended dumb serial driver options' CONFIG_SERIAL_EXTENDED diff -Naur linux-2.2.14/drivers/char/serial.c linux-2.2.14-NecDuart/drivers/char/serial.c --- linux-2.2.14/drivers/char/serial.c Tue Apr 25 16:21:13 2000 +++ linux-2.2.14-NecDuart/drivers/char/serial.c Sun Dec 10 13:43:17 2000 @@ -154,8 +154,13 @@ #define _INLINE_ inline #endif + static char *serial_name = "Serial driver"; -static char *serial_version = "4.27"; +static char *serial_version = "4.27" +#ifdef CONFIG_NEC_DUART_SUPPORT + " - NECDUART fix 1.0" +#endif + ; static DECLARE_TASK_QUEUE(tq_serial); @@ -199,6 +204,18 @@ { "ST16650V2", 32, UART_CLEAR_FIFO | UART_USE_FIFO | UART_STARTECH }, { "TI16750", 64, UART_CLEAR_FIFO | UART_USE_FIFO}, +#ifdef CONFIG_NEC_DUART_SUPPORT + /* The NEC NS16C55x is actually a 16550A. If I added + * a specific record for this particular chipset in "uart_config", + * I would have to change both "serial.h" and the "setserial" + * utility. I don't think it's worth. Anyway, this source is + * ready to accept the change. */ +#ifndef PORT_NS16C550AF +#define PORT_NS16C550AF (PORT_MAX + 1) +#else + { "NS16C55x", 16, UART_CLEAR_FIFO | UART_USE_FIFO }, +#endif +#endif { 0, 0} }; @@ -2821,6 +2838,10 @@ static _INLINE_ void show_serial_version(void) { printk(KERN_INFO "%s version %s with", serial_name, serial_version); +#ifdef CONFIG_NEC_DUART_SUPPORT + printk(" NEC_DUART"); +#define SERIAL_OPT +#endif #ifdef CONFIG_HUB6 printk(" HUB-6"); #define SERIAL_OPT @@ -2959,8 +2980,8 @@ scratch2 = serial_inp(info, UART_IER); serial_outp(info, UART_IER, scratch); if (scratch2) { - restore_flags(flags); - return; /* We failed; there's nothing here */ + /* We failed; there's nothing here */ + goto autoconfig_END_fail; } /* @@ -2979,8 +3000,7 @@ status1 = serial_inp(info, UART_MSR) & 0xF0; serial_outp(info, UART_MCR, scratch); if (status1 != 0x90) { - restore_flags(flags); - return; + goto autoconfig_END_fail; } } @@ -3008,13 +3028,61 @@ /* Check for Startech UART's */ serial_outp(info, UART_LCR, scratch2 | UART_LCR_DLAB); if (serial_in(info, UART_EFR) == 0) { +#ifdef CONFIG_NEC_DUART_SUPPORT + /* [EMAIL PROTECTED]: Both ST16650 and NS16C55x + * behaves the same, so the ortiginal algorithm is not + * enough to detect them. We have to do something + * smarter. */ + /* OK, there could be an ST16650 or a NEC NS16C55x. + * The former uses many bits in EFR(=FCR+DLAB), the latter + * uses only bits 0, 1 and 2, and zeroes other bits. + * So, we'll try to detect an ST16650 by setting some + * high bit in EFR (e.g. the CTS protocol enabler), and + * then verifying if it has really been set. */ + serial_outp(info, UART_LCR, 0xBF); + serial_outp(info, UART_EFR, UART_EFR_CTS); + serial_outp(info, UART_LCR, 0xBF); + if (serial_in(info, UART_EFR) & UART_EFR_CTS) { + /* bit set succeeded. It's an ST16650. */ + state->type = PORT_16650; + serial_outp(info, UART_LCR, 0xBF); + serial_outp(info, UART_EFR, 0); + } + else { + /* bit set unsuccesful. Perhaps we are using a NEC double +UART. + * We must remeber it, because + * - this chip fully supports the 166550A mode, + * - this chip doesn't like the TI 16750 + * detection algorithm, so we must avoid it. */ + state->type = PORT_NS16C550AF; + serial_outp(info, UART_LCR, scratch2); + } +#else /* CONFIG_NEC_DUART_SUPPORT */ state->type = PORT_16650; +#endif /* CONFIG_NEC_DUART_SUPPORT */ } else { serial_outp(info, UART_LCR, 0xBF); if (serial_in(info, UART_EFR) == 0) state->type = PORT_16650V2; } } +#ifdef CONFIG_NEC_DUART_SUPPORT + if (state->type == PORT_NS16C550AF) { +#if PORT_NS16C550AF == (PORT_MAX + 1) + /* Hide the "internal" type code. I'm too lazy to change + * serail.h, setserial,... But the code is ready. */ + state->type = PORT_16550A; +#endif + serial_outp(info, UART_FCR, UART_FCR_ENABLE_FIFO); + } + else + /* [EMAIL PROTECTED]: Arrrgh! This messes up the NEC + * NS16C552A dual UART. Bit 0 of FCR+DLAB enables concurrent write + * on registers of BOTH UARTs. So, with this chip we can't write + * the ENABLE_FIFO bit when DLAB is set. That's why we won't + * execute this code if we detected a NS16C55x (and, anyway, + * we have already detected the chip!). */ +#endif /* CONFIG_NEC_DUART_SUPPORT */ if (state->type == PORT_16550A) { /* Check for TI 16750 */ serial_outp(info, UART_LCR, scratch2 | UART_LCR_DLAB); @@ -3044,8 +3112,7 @@ state->xmit_fifo_size = uart_config[state->type].dfl_xmit_fifo_size; if (state->type == PORT_UNKNOWN) { - restore_flags(flags); - return; + goto autoconfig_END_fail; } request_region(info->port,8,"serial(auto)"); @@ -3066,8 +3133,13 @@ UART_FCR_CLEAR_XMIT)); (void)serial_in(info, UART_RX); serial_outp(info, UART_IER, 0); - + +autoconfig_END: restore_flags(flags); + return; + +autoconfig_END_fail: + goto autoconfig_END; } int register_serial(struct serial_struct *req);