Linux 2.1 kernel patches for serial driver

tytso@mit.edu
Mon, 25 May 1998 00:26:57 -0400


Hi Linus,

The following patches to the serial driver clean up the interrupt
handling (no more cti/sti pairs; we use save_flags/restore_flags now).
It also fixes a race condition in block_til_ready that occurs when a
hangup happens during a blocked open. Finally, it adds a bit more
flexibility to the new autodetection code which should some stuff
agencies.

Could you please apply these patches to your 2.1 development kernel
sources? Thanks!!

- Ted

Patch generated: on Sun May 24 14:09:17 EDT 1998 by tytso@rsts-11.mit.edu
against Linux version 2.1.103

===================================================================
RCS file: drivers/char/RCS/serial.c,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/serial.c
--- drivers/char/serial.c 1998/05/24 18:08:22 1.1
+++ drivers/char/serial.c 1998/05/24 18:08:55
@@ -154,7 +154,7 @@
#endif

static char *serial_name = "Serial driver";
-static char *serial_version = "4.25";
+static char *serial_version = "4.26";

static DECLARE_TASK_QUEUE(tq_serial);

@@ -830,13 +830,14 @@
static unsigned long last_strobe = 0;
struct async_struct *info;
unsigned int i;
+ unsigned long flags;

if ((jiffies - last_strobe) >= RS_STROBE_TIME) {
for (i=1; i < NR_IRQS; i++) {
info = IRQ_ports[i];
if (!info)
continue;
- cli();
+ save_flags(flags); cli();
#ifdef CONFIG_SERIAL_SHARE_IRQ
if (info->next_port) {
do {
@@ -854,7 +855,7 @@
} else
#endif /* CONFIG_SERIAL_SHARE_IRQ */
rs_interrupt_single(i, NULL, NULL);
- sti();
+ restore_flags(flags);
}
}
last_strobe = jiffies;
@@ -862,13 +863,13 @@
timer_active |= 1 << RS_TIMER;

if (IRQ_ports[0]) {
- cli();
+ save_flags(flags); cli();
#ifdef CONFIG_SERIAL_SHARE_IRQ
rs_interrupt(0, NULL, NULL);
#else
rs_interrupt_single(0, NULL, NULL);
#endif
- sti();
+ restore_flags(flags);

timer_table[RS_TIMER].expires = jiffies + IRQ_timeout[0] - 2;
}
@@ -1501,12 +1502,13 @@
static void rs_flush_buffer(struct tty_struct *tty)
{
struct async_struct *info = (struct async_struct *)tty->driver_data;
-
+ unsigned long flags;
+
if (serial_paranoia_check(info, tty->device, "rs_flush_buffer"))
return;
- cli();
+ save_flags(flags); cli();
info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
- sti();
+ restore_flags(flags);
wake_up_interruptible(&tty->write_wait);
if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
tty->ldisc.write_wakeup)
@@ -1543,6 +1545,7 @@
static void rs_throttle(struct tty_struct * tty)
{
struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
#ifdef SERIAL_DEBUG_THROTTLE
char buf[64];

@@ -1559,14 +1562,15 @@
if (tty->termios->c_cflag & CRTSCTS)
info->MCR &= ~UART_MCR_RTS;

- cli();
+ save_flags(flags); cli();
serial_out(info, UART_MCR, info->MCR);
- sti();
+ restore_flags(flags);
}

static void rs_unthrottle(struct tty_struct * tty)
{
struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
#ifdef SERIAL_DEBUG_THROTTLE
char buf[64];

@@ -1585,9 +1589,9 @@
}
if (tty->termios->c_cflag & CRTSCTS)
info->MCR |= UART_MCR_RTS;
- cli();
+ save_flags(flags); cli();
serial_out(info, UART_MCR, info->MCR);
- sti();
+ restore_flags(flags);
}

/*
@@ -1656,7 +1660,9 @@
new_serial.irq = irq_cannonicalize(new_serial.irq);

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

@@ -1745,10 +1751,11 @@
{
unsigned char status;
unsigned int result;
+ unsigned long flags;

- cli();
+ save_flags(flags); cli();
status = serial_in(info, UART_LSR);
- sti();
+ restore_flags(flags);
result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
return put_user(result,value);
}
@@ -1758,11 +1765,12 @@
{
unsigned char control, status;
unsigned int result;
+ unsigned long flags;

control = info->MCR;
- cli();
+ save_flags(flags); cli();
status = serial_in(info, UART_MSR);
- sti();
+ restore_flags(flags);
result = ((control & UART_MCR_RTS) ? TIOCM_RTS : 0)
| ((control & UART_MCR_DTR) ? TIOCM_DTR : 0)
#ifdef TIOCM_OUT1
@@ -1781,6 +1789,7 @@
{
int error;
unsigned int arg;
+ unsigned long flags;

error = get_user(arg, value);
if (error)
@@ -1827,9 +1836,9 @@
default:
return -EINVAL;
}
- cli();
+ save_flags(flags); cli();
serial_out(info, UART_MCR, info->MCR);
- sti();
+ restore_flags(flags);
return 0;
}

@@ -1846,7 +1855,9 @@
shutdown(info);

autoconfig(info->state);
- if ((info->state->flags & ASYNC_AUTO_IRQ) && (info->state->port != 0))
+ if ((info->state->flags & ASYNC_AUTO_IRQ) &&
+ (info->state->port != 0) &&
+ (info->state->type != PORT_UNKNOWN))
info->state->irq = detect_uart_irq(info->state);

retval = startup(info);
@@ -2000,7 +2011,8 @@
struct async_struct * info = (struct async_struct *)tty->driver_data;
struct async_icount cprev, cnow; /* kernel counter temps */
struct serial_icounter_struct *p_cuser; /* user space */
-
+ unsigned long flags;
+
if (serial_paranoia_check(info, tty->device, "rs_ioctl"))
return -ENODEV;

@@ -2052,18 +2064,18 @@
* Caller should use TIOCGICOUNT to see which one it was
*/
case TIOCMIWAIT:
- cli();
+ save_flags(flags); cli();
/* note the counters on entry */
cprev = info->state->icount;
- sti();
+ restore_flags(flags);
while (1) {
interruptible_sleep_on(&info->delta_msr_wait);
/* see if a signal did it */
if (signal_pending(current))
return -ERESTARTSYS;
- cli();
+ save_flags(flags); cli();
cnow = info->state->icount; /* atomic copy */
- sti();
+ restore_flags(flags);
if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)
return -EIO; /* no change => error */
@@ -2084,9 +2096,9 @@
* RI where only 0->1 is counted.
*/
case TIOCGICOUNT:
- cli();
+ save_flags(flags); cli();
cnow = info->state->icount;
- sti();
+ restore_flags(flags);
p_cuser = (struct serial_icounter_struct *) arg;
error = put_user(cnow.cts, &p_cuser->cts);
if (error) return error;
@@ -2096,6 +2108,26 @@
if (error) return error;
error = put_user(cnow.dcd, &p_cuser->dcd);
if (error) return error;
+ error = put_user(cnow.rx, &p_cuser->rx);
+ if (error) return error;
+ error = put_user(cnow.tx, &p_cuser->tx);
+ if (error) return error;
+ error = put_user(cnow.frame, &p_cuser->frame);
+ if (error) return error;
+ error = put_user(cnow.overrun, &p_cuser->overrun);
+ if (error) return error;
+ error = put_user(cnow.parity, &p_cuser->parity);
+ if (error) return error;
+ error = put_user(cnow.brk, &p_cuser->brk);
+ if (error) return error;
+ error = put_user(cnow.buf_overrun, &p_cuser->buf_overrun);
+ if (error) return error;
+ return 0;
+
+ case TIOCSERGWILD:
+ case TIOCSERSWILD:
+ /* "setserial -W" is called in Debian boot */
+ printk ("TIOCSER?WILD ioctl obsolete, ignored.\n");
return 0;

default:
@@ -2107,7 +2139,8 @@
static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios)
{
struct async_struct *info = (struct async_struct *)tty->driver_data;
-
+ unsigned long flags;
+
if ( (tty->termios->c_cflag == old_termios->c_cflag)
&& ( RELEVANT_IFLAG(tty->termios->c_iflag)
== RELEVANT_IFLAG(old_termios->c_iflag)))
@@ -2119,9 +2152,9 @@
if ((old_termios->c_cflag & CBAUD) &&
!(tty->termios->c_cflag & CBAUD)) {
info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
- cli();
+ save_flags(flags); cli();
serial_out(info, UART_MCR, info->MCR);
- sti();
+ restore_flags(flags);
}

/* Handle transition away from B0 status */
@@ -2132,9 +2165,9 @@
!test_bit(TTY_THROTTLED, &tty->flags)) {
info->MCR |= UART_MCR_RTS;
}
- cli();
+ save_flags(flags); cli();
serial_out(info, UART_MCR, info->MCR);
- sti();
+ restore_flags(flags);
}

/* Handle turning off CRTSCTS */
@@ -2284,6 +2317,9 @@
if (info->state->type == PORT_UNKNOWN)
return;

+ if (info->xmit_fifo_size == 0)
+ return; /* Just in case.... */
+
orig_jiffies = jiffies;
/*
* Set the check interval to be 1/5 of the estimated time to
@@ -2355,7 +2391,8 @@
struct wait_queue wait = { current, NULL };
struct serial_state *state = info->state;
int retval;
- int do_clocal = 0;
+ int do_clocal = 0, extra_count = 0;
+ unsigned long flags;

/*
* If the device is in the middle of being closed, then block
@@ -2425,19 +2462,21 @@
printk("block_til_ready before block: ttys%d, count = %d\n",
state->line, state->count);
#endif
- cli();
- if (!tty_hung_up_p(filp))
+ save_flags(flags); cli();
+ if (!tty_hung_up_p(filp)) {
+ extra_count = 1;
state->count--;
- sti();
+ }
+ restore_flags(flags);
info->blocked_open++;
while (1) {
- cli();
+ save_flags(flags); cli();
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));
- sti();
+ restore_flags(flags);
current->state = TASK_INTERRUPTIBLE;
if (tty_hung_up_p(filp) ||
!(info->flags & ASYNC_INITIALIZED)) {
@@ -2468,7 +2507,7 @@
}
current->state = TASK_RUNNING;
remove_wait_queue(&info->open_wait, &wait);
- if (!tty_hung_up_p(filp))
+ if (extra_count)
state->count++;
info->blocked_open--;
#ifdef SERIAL_DEBUG_OPEN
@@ -2619,6 +2658,7 @@
struct async_struct *info = state->info, scr_info;
char stat_buf[30], control, status;
int ret;
+ unsigned long flags;

ret = sprintf(buf, "%d: uart:%s port:%X irq:%d",
state->line, uart_config[state->type].name,
@@ -2641,10 +2681,10 @@
info->quot = 0;
info->tty = 0;
}
- cli();
+ save_flags(flags); cli();
status = serial_in(info, UART_MSR);
control = info ? info->MCR : serial_in(info, UART_MCR);
- sti();
+ restore_flags(flags);

stat_buf[0] = 0;
stat_buf[1] = 0;
@@ -2691,12 +2731,13 @@
int rs_read_proc(char *page, char **start, off_t off, int count,
int *eof, void *data)
{
- int i, len = 0;
+ int i, len = 0, l;
off_t begin = 0;

len += sprintf(page, "serinfo:1.0 driver:%s\n", serial_version);
for (i = 0; i < NR_PORTS && len < 4000; i++) {
- len += line_info(page + len, &rs_table[i]);
+ l = line_info(page + len, &rs_table[i]);
+ len += l;
if (len+begin > off+count)
goto done;
if (len+begin < off) {
@@ -2742,10 +2783,11 @@
#endif
#ifdef CONFIG_SERIAL_SHARE_IRQ
printk(" SHARE_IRQ");
-#endif
#define SERIAL_OPT
+#endif
#ifdef CONFIG_SERIAL_DETECT_IRQ
printk(" DETECT_IRQ");
+#define SERIAL_OPT
#endif
#ifdef SERIAL_OPT
printk(" enabled\n");
@@ -2766,7 +2808,7 @@
{
int irq;
unsigned long irqs;
- unsigned char save_mcr;
+ unsigned char save_mcr, save_ier;
struct async_struct scr_info; /* serial_{in,out} because HUB6 */

#ifdef CONFIG_SERIAL_MANY_PORTS
@@ -2790,15 +2832,30 @@
/* forget possible initially masked and pending IRQ */
probe_irq_off(probe_irq_on());
save_mcr = serial_inp(&scr_info, UART_MCR);
-
+ save_ier = serial_inp(&scr_info, UART_IER);
serial_outp(&scr_info, UART_MCR, UART_MCR_OUT1 | UART_MCR_OUT2);
+
irqs = probe_irq_on();
serial_outp(&scr_info, UART_MCR, 0);
- udelay (1);
+ udelay (10);
+ if (state->flags & ASYNC_FOURPORT) {
+ serial_outp(&scr_info, UART_MCR,
+ UART_MCR_DTR | UART_MCR_RTS);
+ } else {
+ serial_outp(&scr_info, UART_MCR,
+ UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
+ }
+ serial_outp(&scr_info, UART_IER, 0x0f); /* enable all intrs */
+ (void)serial_inp(&scr_info, UART_LSR);
+ (void)serial_inp(&scr_info, UART_RX);
+ (void)serial_inp(&scr_info, UART_IIR);
+ (void)serial_inp(&scr_info, UART_MSR);
+ serial_outp(&scr_info, UART_TX, 0xFF);
+ udelay (20);
irq = probe_irq_off(irqs);

serial_outp(&scr_info, UART_MCR, save_mcr);
-
+ serial_outp(&scr_info, UART_IER, save_ier);
#ifdef CONFIG_SERIAL_MANY_PORTS
if (state->flags & ASYNC_FOURPORT)
outb_p(save_ICP, ICP);
@@ -2866,11 +2923,9 @@
if (!(state->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;
@@ -3083,12 +3138,9 @@
state->icount.frame = state->icount.parity = 0;
state->icount.overrun = state->icount.brk = 0;
state->irq = irq_cannonicalize(state->irq);
- if (check_region(state->port,8)) {
- state->type = PORT_UNKNOWN;
+ if (check_region(state->port,8))
continue;
- }
- if ( (state->type == PORT_UNKNOWN)
- && (state->flags & ASYNC_BOOT_AUTOCONF))
+ if (state->flags & ASYNC_BOOT_AUTOCONF)
autoconfig(state);
}
/*
===================================================================
RCS file: include/linux/RCS/serial_reg.h,v
retrieving revision 1.1
diff -u -r1.1 include/linux/serial_reg.h
--- include/linux/serial_reg.h 1998/05/24 18:08:22 1.1
+++ include/linux/serial_reg.h 1998/05/24 18:08:55
@@ -1,5 +1,5 @@
/*
- * include/linux/serial.h
+ * include/linux/serial_reg.h
*
* Copyright (C) 1992, 1994 by Theodore Ts'o.
*

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu