From 68576cf122bc5195c758ed295e78b5858472378a Mon Sep 17 00:00:00 2001 From: Thomas Bogendoerfer Date: Wed, 28 Nov 2007 16:21:44 -0800 Subject: IP22ZILOG: fix lockup and sysrq - fix lockup when switching from early console to real console - make sysrq reliable - fix panic, if sysrq is issued before console is opened Signed-off-by: Thomas Bogendoerfer Acked-by: Ralf Baechle Cc: Alan Cox Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/serial/ip22zilog.c | 247 +++++++++++++++++++-------------------------- 1 file changed, 106 insertions(+), 141 deletions(-) (limited to 'drivers/serial/ip22zilog.c') diff --git a/drivers/serial/ip22zilog.c b/drivers/serial/ip22zilog.c index f3257f708ef9..9c95bc0398ad 100644 --- a/drivers/serial/ip22zilog.c +++ b/drivers/serial/ip22zilog.c @@ -45,8 +45,6 @@ #include "ip22zilog.h" -void ip22_do_break(void); - /* * On IP22 we need to delay after register accesses but we do not need to * flush writes. @@ -81,12 +79,9 @@ struct uart_ip22zilog_port { #define IP22ZILOG_FLAG_REGS_HELD 0x00000040 #define IP22ZILOG_FLAG_TX_STOPPED 0x00000080 #define IP22ZILOG_FLAG_TX_ACTIVE 0x00000100 +#define IP22ZILOG_FLAG_RESET_DONE 0x00000200 - unsigned int cflag; - - /* L1-A keyboard break state. */ - int kbd_id; - int l1_down; + unsigned int tty_break; unsigned char parity_mask; unsigned char prev_status; @@ -250,13 +245,26 @@ static void ip22zilog_maybe_update_regs(struct uart_ip22zilog_port *up, } } -static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up, - struct zilog_channel *channel) +#define Rx_BRK 0x0100 /* BREAK event software flag. */ +#define Rx_SYS 0x0200 /* SysRq event software flag. */ + +static struct tty_struct *ip22zilog_receive_chars(struct uart_ip22zilog_port *up, + struct zilog_channel *channel) { - struct tty_struct *tty = up->port.info->tty; /* XXX info==NULL? */ + struct tty_struct *tty; + unsigned char ch, flag; + unsigned int r1; + + tty = NULL; + if (up->port.info != NULL && + up->port.info->tty != NULL) + tty = up->port.info->tty; - while (1) { - unsigned char ch, r1, flag; + for (;;) { + ch = readb(&channel->control); + ZSDELAY(); + if (!(ch & Rx_CH_AV)) + break; r1 = read_zsreg(channel, R1); if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) { @@ -265,43 +273,26 @@ static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up, ZS_WSYNC(channel); } - ch = readb(&channel->control); - ZSDELAY(); - - /* This funny hack depends upon BRK_ABRT not interfering - * with the other bits we care about in R1. - */ - if (ch & BRK_ABRT) - r1 |= BRK_ABRT; - ch = readb(&channel->data); ZSDELAY(); ch &= up->parity_mask; - if (ZS_IS_CONS(up) && (r1 & BRK_ABRT)) { - /* Wait for BREAK to deassert to avoid potentially - * confusing the PROM. - */ - while (1) { - ch = readb(&channel->control); - ZSDELAY(); - if (!(ch & BRK_ABRT)) - break; - } - ip22_do_break(); - return; - } + /* Handle the null char got when BREAK is removed. */ + if (!ch) + r1 |= up->tty_break; /* A real serial line, record the character and status. */ flag = TTY_NORMAL; up->port.icount.rx++; - if (r1 & (BRK_ABRT | PAR_ERR | Rx_OVR | CRC_ERR)) { - if (r1 & BRK_ABRT) { - r1 &= ~(PAR_ERR | CRC_ERR); + if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR | Rx_SYS | Rx_BRK)) { + up->tty_break = 0; + + if (r1 & (Rx_SYS | Rx_BRK)) { up->port.icount.brk++; - if (uart_handle_break(&up->port)) - goto next_char; + if (r1 & Rx_SYS) + continue; + r1 &= ~(PAR_ERR | CRC_ERR); } else if (r1 & PAR_ERR) up->port.icount.parity++; @@ -310,30 +301,21 @@ static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up, if (r1 & Rx_OVR) up->port.icount.overrun++; r1 &= up->port.read_status_mask; - if (r1 & BRK_ABRT) + if (r1 & Rx_BRK) flag = TTY_BREAK; else if (r1 & PAR_ERR) flag = TTY_PARITY; else if (r1 & CRC_ERR) flag = TTY_FRAME; } - if (uart_handle_sysrq_char(&up->port, ch)) - goto next_char; - if (up->port.ignore_status_mask == 0xff || - (r1 & up->port.ignore_status_mask) == 0) - tty_insert_flip_char(tty, ch, flag); + if (uart_handle_sysrq_char(&up->port, ch)) + continue; - if (r1 & Rx_OVR) - tty_insert_flip_char(tty, 0, TTY_OVERRUN); - next_char: - ch = readb(&channel->control); - ZSDELAY(); - if (!(ch & Rx_CH_AV)) - break; + if (tty) + uart_insert_char(&up->port, r1, Rx_OVR, ch, flag); } - - tty_flip_buffer_push(tty); + return tty; } static void ip22zilog_status_handle(struct uart_ip22zilog_port *up, @@ -348,6 +330,15 @@ static void ip22zilog_status_handle(struct uart_ip22zilog_port *up, ZSDELAY(); ZS_WSYNC(channel); + if (up->curregs[R15] & BRKIE) { + if ((status & BRK_ABRT) && !(up->prev_status & BRK_ABRT)) { + if (uart_handle_break(&up->port)) + up->tty_break = Rx_SYS; + else + up->tty_break = Rx_BRK; + } + } + if (ZS_WANTS_MODEM_STATUS(up)) { if (status & SYNC) up->port.icount.dsr++; @@ -356,10 +347,10 @@ static void ip22zilog_status_handle(struct uart_ip22zilog_port *up, * But it does not tell us which bit has changed, we have to keep * track of this ourselves. */ - if ((status & DCD) ^ up->prev_status) + if ((status ^ up->prev_status) ^ DCD) uart_handle_dcd_change(&up->port, (status & DCD)); - if ((status & CTS) ^ up->prev_status) + if ((status ^ up->prev_status) ^ CTS) uart_handle_cts_change(&up->port, (status & CTS)); @@ -447,19 +438,21 @@ static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id) while (up) { struct zilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(&up->port); + struct tty_struct *tty; unsigned char r3; spin_lock(&up->port.lock); r3 = read_zsreg(channel, R3); /* Channel A */ + tty = NULL; if (r3 & (CHAEXT | CHATxIP | CHARxIP)) { writeb(RES_H_IUS, &channel->control); ZSDELAY(); ZS_WSYNC(channel); if (r3 & CHARxIP) - ip22zilog_receive_chars(up, channel); + tty = ip22zilog_receive_chars(up, channel); if (r3 & CHAEXT) ip22zilog_status_handle(up, channel); if (r3 & CHATxIP) @@ -467,18 +460,22 @@ static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id) } spin_unlock(&up->port.lock); + if (tty) + tty_flip_buffer_push(tty); + /* Channel B */ up = up->next; channel = ZILOG_CHANNEL_FROM_PORT(&up->port); spin_lock(&up->port.lock); + tty = NULL; if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) { writeb(RES_H_IUS, &channel->control); ZSDELAY(); ZS_WSYNC(channel); if (r3 & CHBRxIP) - ip22zilog_receive_chars(up, channel); + tty = ip22zilog_receive_chars(up, channel); if (r3 & CHBEXT) ip22zilog_status_handle(up, channel); if (r3 & CHBTxIP) @@ -486,6 +483,9 @@ static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id) } spin_unlock(&up->port.lock); + if (tty) + tty_flip_buffer_push(tty); + up = up->next; } @@ -681,11 +681,46 @@ static void ip22zilog_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&port->lock, flags); } +static void __ip22zilog_reset(struct uart_ip22zilog_port *up) +{ + struct zilog_channel *channel; + int i; + + if (up->flags & IP22ZILOG_FLAG_RESET_DONE) + return; + + /* Let pending transmits finish. */ + channel = ZILOG_CHANNEL_FROM_PORT(&up->port); + for (i = 0; i < 1000; i++) { + unsigned char stat = read_zsreg(channel, R1); + if (stat & ALL_SNT) + break; + udelay(100); + } + + if (!ZS_IS_CHANNEL_A(up)) { + up++; + channel = ZILOG_CHANNEL_FROM_PORT(&up->port); + } + write_zsreg(channel, R9, FHWRES); + ZSDELAY_LONG(); + (void) read_zsreg(channel, R0); + + up->flags |= IP22ZILOG_FLAG_RESET_DONE; + up->next->flags |= IP22ZILOG_FLAG_RESET_DONE; +} + static void __ip22zilog_startup(struct uart_ip22zilog_port *up) { struct zilog_channel *channel; channel = ZILOG_CHANNEL_FROM_PORT(&up->port); + + __ip22zilog_reset(up); + + __load_zsregs(channel, up->curregs); + /* set master interrupt enable */ + write_zsreg(channel, R9, up->curregs[R9]); up->prev_status = readb(&channel->control); /* Enable receiver and transmitter. */ @@ -859,8 +894,6 @@ ip22zilog_set_termios(struct uart_port *port, struct ktermios *termios, else up->flags &= ~IP22ZILOG_FLAG_MODEM_STATUS; - up->cflag = termios->c_cflag; - ip22zilog_maybe_update_regs(up, ZILOG_CHANNEL_FROM_PORT(port)); uart_update_timeout(port, termios->c_cflag, baud); @@ -992,74 +1025,29 @@ ip22zilog_console_write(struct console *con, const char *s, unsigned int count) spin_unlock_irqrestore(&up->port.lock, flags); } -void -ip22serial_console_termios(struct console *con, char *options) -{ - int baud = 9600, bits = 8, cflag; - int parity = 'n'; - int flow = 'n'; - - if (options) - uart_parse_options(options, &baud, &parity, &bits, &flow); - - cflag = CREAD | HUPCL | CLOCAL; - - switch (baud) { - case 150: cflag |= B150; break; - case 300: cflag |= B300; break; - case 600: cflag |= B600; break; - case 1200: cflag |= B1200; break; - case 2400: cflag |= B2400; break; - case 4800: cflag |= B4800; break; - case 9600: cflag |= B9600; break; - case 19200: cflag |= B19200; break; - case 38400: cflag |= B38400; break; - default: baud = 9600; cflag |= B9600; break; - } - - con->cflag = cflag | CS8; /* 8N1 */ - - uart_update_timeout(&ip22zilog_port_table[con->index].port, cflag, baud); -} - static int __init ip22zilog_console_setup(struct console *con, char *options) { struct uart_ip22zilog_port *up = &ip22zilog_port_table[con->index]; unsigned long flags; - int baud, brg; - - printk("Console: ttyS%d (IP22-Zilog)\n", con->index); + int baud = 9600, bits = 8; + int parity = 'n'; + int flow = 'n'; - /* Get firmware console settings. */ - ip22serial_console_termios(con, options); + up->flags |= IP22ZILOG_FLAG_IS_CONS; - /* Firmware console speed is limited to 150-->38400 baud so - * this hackish cflag thing is OK. - */ - switch (con->cflag & CBAUD) { - case B150: baud = 150; break; - case B300: baud = 300; break; - case B600: baud = 600; break; - case B1200: baud = 1200; break; - case B2400: baud = 2400; break; - case B4800: baud = 4800; break; - default: case B9600: baud = 9600; break; - case B19200: baud = 19200; break; - case B38400: baud = 38400; break; - }; - - brg = BPS_TO_BRG(baud, ZS_CLOCK / ZS_CLOCK_DIVISOR); + printk(KERN_INFO "Console: ttyS%d (IP22-Zilog)\n", con->index); spin_lock_irqsave(&up->port.lock, flags); - up->curregs[R15] = BRKIE; - ip22zilog_convert_to_zs(up, con->cflag, 0, brg); + up->curregs[R15] |= BRKIE; __ip22zilog_startup(up); spin_unlock_irqrestore(&up->port.lock, flags); - return 0; + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + return uart_set_options(&up->port, con, baud, parity, bits, flow); } static struct uart_driver ip22zilog_reg; @@ -1140,25 +1128,10 @@ static void __init ip22zilog_prepare(void) up[(chip * 2) + 1].port.line = (chip * 2) + 1; up[(chip * 2) + 1].flags |= IP22ZILOG_FLAG_IS_CHANNEL_A; } -} - -static void __init ip22zilog_init_hw(void) -{ - int i; - - for (i = 0; i < NUM_CHANNELS; i++) { - struct uart_ip22zilog_port *up = &ip22zilog_port_table[i]; - struct zilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(&up->port); - unsigned long flags; - int baud, brg; - spin_lock_irqsave(&up->port.lock, flags); - - if (ZS_IS_CHANNEL_A(up)) { - write_zsreg(channel, R9, FHWRES); - ZSDELAY_LONG(); - (void) read_zsreg(channel, R0); - } + for (channel = 0; channel < NUM_CHANNELS; channel++) { + struct uart_ip22zilog_port *up = &ip22zilog_port_table[channel]; + int brg; /* Normal serial TTY. */ up->parity_mask = 0xff; @@ -1169,16 +1142,10 @@ static void __init ip22zilog_init_hw(void) up->curregs[R9] = NV | MIE; up->curregs[R10] = NRZ; up->curregs[R11] = TCBR | RCBR; - baud = 9600; - brg = BPS_TO_BRG(baud, ZS_CLOCK / ZS_CLOCK_DIVISOR); + brg = BPS_TO_BRG(9600, ZS_CLOCK / ZS_CLOCK_DIVISOR); up->curregs[R12] = (brg & 0xff); up->curregs[R13] = (brg >> 8) & 0xff; up->curregs[R14] = BRENAB; - __load_zsregs(channel, up->curregs); - /* set master interrupt enable */ - write_zsreg(channel, R9, up->curregs[R9]); - - spin_unlock_irqrestore(&up->port.lock, flags); } } @@ -1195,8 +1162,6 @@ static int __init ip22zilog_ports_init(void) panic("IP22-Zilog: Unable to register zs interrupt handler.\n"); } - ip22zilog_init_hw(); - ret = uart_register_driver(&ip22zilog_reg); if (ret == 0) { int i; -- cgit v1.2.3