Commit 68576cf1 authored by Thomas Bogendoerfer's avatar Thomas Bogendoerfer Committed by Linus Torvalds

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: default avatarThomas Bogendoerfer <tsbogend@alpha.franken.de>
Acked-by: default avatarRalf Baechle <ralf@linux-mips.org>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 6d4f5879
...@@ -31,25 +31,6 @@ ...@@ -31,25 +31,6 @@
unsigned long sgi_gfxaddr; unsigned long sgi_gfxaddr;
EXPORT_SYMBOL_GPL(sgi_gfxaddr); EXPORT_SYMBOL_GPL(sgi_gfxaddr);
/*
* Stop-A is originally a Sun thing that isn't standard on IP22 so to avoid
* accidents it's disabled by default on IP22.
*
* FIXME: provide a mechanism to change the value of stop_a_enabled.
*/
int stop_a_enabled;
void ip22_do_break(void)
{
if (!stop_a_enabled)
return;
printk("\n");
ArcEnterInteractiveMode();
}
EXPORT_SYMBOL(ip22_do_break);
extern void ip22_be_init(void) __init; extern void ip22_be_init(void) __init;
void __init plat_mem_setup(void) void __init plat_mem_setup(void)
......
...@@ -45,8 +45,6 @@ ...@@ -45,8 +45,6 @@
#include "ip22zilog.h" #include "ip22zilog.h"
void ip22_do_break(void);
/* /*
* On IP22 we need to delay after register accesses but we do not need to * On IP22 we need to delay after register accesses but we do not need to
* flush writes. * flush writes.
...@@ -81,12 +79,9 @@ struct uart_ip22zilog_port { ...@@ -81,12 +79,9 @@ struct uart_ip22zilog_port {
#define IP22ZILOG_FLAG_REGS_HELD 0x00000040 #define IP22ZILOG_FLAG_REGS_HELD 0x00000040
#define IP22ZILOG_FLAG_TX_STOPPED 0x00000080 #define IP22ZILOG_FLAG_TX_STOPPED 0x00000080
#define IP22ZILOG_FLAG_TX_ACTIVE 0x00000100 #define IP22ZILOG_FLAG_TX_ACTIVE 0x00000100
#define IP22ZILOG_FLAG_RESET_DONE 0x00000200
unsigned int cflag; unsigned int tty_break;
/* L1-A keyboard break state. */
int kbd_id;
int l1_down;
unsigned char parity_mask; unsigned char parity_mask;
unsigned char prev_status; unsigned char prev_status;
...@@ -250,13 +245,26 @@ static void ip22zilog_maybe_update_regs(struct uart_ip22zilog_port *up, ...@@ -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, #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 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) { for (;;) {
unsigned char ch, r1, flag; ch = readb(&channel->control);
ZSDELAY();
if (!(ch & Rx_CH_AV))
break;
r1 = read_zsreg(channel, R1); r1 = read_zsreg(channel, R1);
if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) { if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) {
...@@ -265,43 +273,26 @@ static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up, ...@@ -265,43 +273,26 @@ static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up,
ZS_WSYNC(channel); 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); ch = readb(&channel->data);
ZSDELAY(); ZSDELAY();
ch &= up->parity_mask; ch &= up->parity_mask;
if (ZS_IS_CONS(up) && (r1 & BRK_ABRT)) { /* Handle the null char got when BREAK is removed. */
/* Wait for BREAK to deassert to avoid potentially if (!ch)
* confusing the PROM. r1 |= up->tty_break;
*/
while (1) {
ch = readb(&channel->control);
ZSDELAY();
if (!(ch & BRK_ABRT))
break;
}
ip22_do_break();
return;
}
/* A real serial line, record the character and status. */ /* A real serial line, record the character and status. */
flag = TTY_NORMAL; flag = TTY_NORMAL;
up->port.icount.rx++; up->port.icount.rx++;
if (r1 & (BRK_ABRT | PAR_ERR | Rx_OVR | CRC_ERR)) { if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR | Rx_SYS | Rx_BRK)) {
if (r1 & BRK_ABRT) { up->tty_break = 0;
r1 &= ~(PAR_ERR | CRC_ERR);
if (r1 & (Rx_SYS | Rx_BRK)) {
up->port.icount.brk++; up->port.icount.brk++;
if (uart_handle_break(&up->port)) if (r1 & Rx_SYS)
goto next_char; continue;
r1 &= ~(PAR_ERR | CRC_ERR);
} }
else if (r1 & PAR_ERR) else if (r1 & PAR_ERR)
up->port.icount.parity++; up->port.icount.parity++;
...@@ -310,30 +301,21 @@ static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up, ...@@ -310,30 +301,21 @@ static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up,
if (r1 & Rx_OVR) if (r1 & Rx_OVR)
up->port.icount.overrun++; up->port.icount.overrun++;
r1 &= up->port.read_status_mask; r1 &= up->port.read_status_mask;
if (r1 & BRK_ABRT) if (r1 & Rx_BRK)
flag = TTY_BREAK; flag = TTY_BREAK;
else if (r1 & PAR_ERR) else if (r1 & PAR_ERR)
flag = TTY_PARITY; flag = TTY_PARITY;
else if (r1 & CRC_ERR) else if (r1 & CRC_ERR)
flag = TTY_FRAME; flag = TTY_FRAME;
} }
if (uart_handle_sysrq_char(&up->port, ch))
goto next_char;
if (up->port.ignore_status_mask == 0xff || if (uart_handle_sysrq_char(&up->port, ch))
(r1 & up->port.ignore_status_mask) == 0) continue;
tty_insert_flip_char(tty, ch, flag);
if (r1 & Rx_OVR) if (tty)
tty_insert_flip_char(tty, 0, TTY_OVERRUN); uart_insert_char(&up->port, r1, Rx_OVR, ch, flag);
next_char:
ch = readb(&channel->control);
ZSDELAY();
if (!(ch & Rx_CH_AV))
break;
} }
return tty;
tty_flip_buffer_push(tty);
} }
static void ip22zilog_status_handle(struct uart_ip22zilog_port *up, static void ip22zilog_status_handle(struct uart_ip22zilog_port *up,
...@@ -348,6 +330,15 @@ 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(); ZSDELAY();
ZS_WSYNC(channel); 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 (ZS_WANTS_MODEM_STATUS(up)) {
if (status & SYNC) if (status & SYNC)
up->port.icount.dsr++; up->port.icount.dsr++;
...@@ -356,10 +347,10 @@ static void ip22zilog_status_handle(struct uart_ip22zilog_port *up, ...@@ -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 * But it does not tell us which bit has changed, we have to keep
* track of this ourselves. * track of this ourselves.
*/ */
if ((status & DCD) ^ up->prev_status) if ((status ^ up->prev_status) ^ DCD)
uart_handle_dcd_change(&up->port, uart_handle_dcd_change(&up->port,
(status & DCD)); (status & DCD));
if ((status & CTS) ^ up->prev_status) if ((status ^ up->prev_status) ^ CTS)
uart_handle_cts_change(&up->port, uart_handle_cts_change(&up->port,
(status & CTS)); (status & CTS));
...@@ -447,19 +438,21 @@ static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id) ...@@ -447,19 +438,21 @@ static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id)
while (up) { while (up) {
struct zilog_channel *channel struct zilog_channel *channel
= ZILOG_CHANNEL_FROM_PORT(&up->port); = ZILOG_CHANNEL_FROM_PORT(&up->port);
struct tty_struct *tty;
unsigned char r3; unsigned char r3;
spin_lock(&up->port.lock); spin_lock(&up->port.lock);
r3 = read_zsreg(channel, R3); r3 = read_zsreg(channel, R3);
/* Channel A */ /* Channel A */
tty = NULL;
if (r3 & (CHAEXT | CHATxIP | CHARxIP)) { if (r3 & (CHAEXT | CHATxIP | CHARxIP)) {
writeb(RES_H_IUS, &channel->control); writeb(RES_H_IUS, &channel->control);
ZSDELAY(); ZSDELAY();
ZS_WSYNC(channel); ZS_WSYNC(channel);
if (r3 & CHARxIP) if (r3 & CHARxIP)
ip22zilog_receive_chars(up, channel); tty = ip22zilog_receive_chars(up, channel);
if (r3 & CHAEXT) if (r3 & CHAEXT)
ip22zilog_status_handle(up, channel); ip22zilog_status_handle(up, channel);
if (r3 & CHATxIP) if (r3 & CHATxIP)
...@@ -467,18 +460,22 @@ static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id) ...@@ -467,18 +460,22 @@ static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id)
} }
spin_unlock(&up->port.lock); spin_unlock(&up->port.lock);
if (tty)
tty_flip_buffer_push(tty);
/* Channel B */ /* Channel B */
up = up->next; up = up->next;
channel = ZILOG_CHANNEL_FROM_PORT(&up->port); channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
spin_lock(&up->port.lock); spin_lock(&up->port.lock);
tty = NULL;
if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) { if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) {
writeb(RES_H_IUS, &channel->control); writeb(RES_H_IUS, &channel->control);
ZSDELAY(); ZSDELAY();
ZS_WSYNC(channel); ZS_WSYNC(channel);
if (r3 & CHBRxIP) if (r3 & CHBRxIP)
ip22zilog_receive_chars(up, channel); tty = ip22zilog_receive_chars(up, channel);
if (r3 & CHBEXT) if (r3 & CHBEXT)
ip22zilog_status_handle(up, channel); ip22zilog_status_handle(up, channel);
if (r3 & CHBTxIP) if (r3 & CHBTxIP)
...@@ -486,6 +483,9 @@ static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id) ...@@ -486,6 +483,9 @@ static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id)
} }
spin_unlock(&up->port.lock); spin_unlock(&up->port.lock);
if (tty)
tty_flip_buffer_push(tty);
up = up->next; up = up->next;
} }
...@@ -681,11 +681,46 @@ static void ip22zilog_break_ctl(struct uart_port *port, int break_state) ...@@ -681,11 +681,46 @@ static void ip22zilog_break_ctl(struct uart_port *port, int break_state)
spin_unlock_irqrestore(&port->lock, flags); 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) static void __ip22zilog_startup(struct uart_ip22zilog_port *up)
{ {
struct zilog_channel *channel; struct zilog_channel *channel;
channel = ZILOG_CHANNEL_FROM_PORT(&up->port); 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); up->prev_status = readb(&channel->control);
/* Enable receiver and transmitter. */ /* Enable receiver and transmitter. */
...@@ -859,8 +894,6 @@ ip22zilog_set_termios(struct uart_port *port, struct ktermios *termios, ...@@ -859,8 +894,6 @@ ip22zilog_set_termios(struct uart_port *port, struct ktermios *termios,
else else
up->flags &= ~IP22ZILOG_FLAG_MODEM_STATUS; up->flags &= ~IP22ZILOG_FLAG_MODEM_STATUS;
up->cflag = termios->c_cflag;
ip22zilog_maybe_update_regs(up, ZILOG_CHANNEL_FROM_PORT(port)); ip22zilog_maybe_update_regs(up, ZILOG_CHANNEL_FROM_PORT(port));
uart_update_timeout(port, termios->c_cflag, baud); uart_update_timeout(port, termios->c_cflag, baud);
...@@ -992,74 +1025,29 @@ ip22zilog_console_write(struct console *con, const char *s, unsigned int count) ...@@ -992,74 +1025,29 @@ ip22zilog_console_write(struct console *con, const char *s, unsigned int count)
spin_unlock_irqrestore(&up->port.lock, flags); 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) static int __init ip22zilog_console_setup(struct console *con, char *options)
{ {
struct uart_ip22zilog_port *up = &ip22zilog_port_table[con->index]; struct uart_ip22zilog_port *up = &ip22zilog_port_table[con->index];
unsigned long flags; unsigned long flags;
int baud, brg; int baud = 9600, bits = 8;
int parity = 'n';
printk("Console: ttyS%d (IP22-Zilog)\n", con->index); int flow = 'n';
/* Get firmware console settings. */
ip22serial_console_termios(con, options);
/* Firmware console speed is limited to 150-->38400 baud so up->flags |= IP22ZILOG_FLAG_IS_CONS;
* 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); spin_lock_irqsave(&up->port.lock, flags);
up->curregs[R15] = BRKIE; up->curregs[R15] |= BRKIE;
ip22zilog_convert_to_zs(up, con->cflag, 0, brg);
__ip22zilog_startup(up); __ip22zilog_startup(up);
spin_unlock_irqrestore(&up->port.lock, flags); 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; static struct uart_driver ip22zilog_reg;
...@@ -1140,25 +1128,10 @@ static void __init ip22zilog_prepare(void) ...@@ -1140,25 +1128,10 @@ static void __init ip22zilog_prepare(void)
up[(chip * 2) + 1].port.line = (chip * 2) + 1; up[(chip * 2) + 1].port.line = (chip * 2) + 1;
up[(chip * 2) + 1].flags |= IP22ZILOG_FLAG_IS_CHANNEL_A; up[(chip * 2) + 1].flags |= IP22ZILOG_FLAG_IS_CHANNEL_A;
} }
}
static void __init ip22zilog_init_hw(void) for (channel = 0; channel < NUM_CHANNELS; channel++) {
{ struct uart_ip22zilog_port *up = &ip22zilog_port_table[channel];
int i; int brg;
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);
}
/* Normal serial TTY. */ /* Normal serial TTY. */
up->parity_mask = 0xff; up->parity_mask = 0xff;
...@@ -1169,16 +1142,10 @@ static void __init ip22zilog_init_hw(void) ...@@ -1169,16 +1142,10 @@ static void __init ip22zilog_init_hw(void)
up->curregs[R9] = NV | MIE; up->curregs[R9] = NV | MIE;
up->curregs[R10] = NRZ; up->curregs[R10] = NRZ;
up->curregs[R11] = TCBR | RCBR; up->curregs[R11] = TCBR | RCBR;
baud = 9600; brg = BPS_TO_BRG(9600, ZS_CLOCK / ZS_CLOCK_DIVISOR);
brg = BPS_TO_BRG(baud, ZS_CLOCK / ZS_CLOCK_DIVISOR);
up->curregs[R12] = (brg & 0xff); up->curregs[R12] = (brg & 0xff);
up->curregs[R13] = (brg >> 8) & 0xff; up->curregs[R13] = (brg >> 8) & 0xff;
up->curregs[R14] = BRENAB; 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) ...@@ -1195,8 +1162,6 @@ static int __init ip22zilog_ports_init(void)
panic("IP22-Zilog: Unable to register zs interrupt handler.\n"); panic("IP22-Zilog: Unable to register zs interrupt handler.\n");
} }
ip22zilog_init_hw();
ret = uart_register_driver(&ip22zilog_reg); ret = uart_register_driver(&ip22zilog_reg);
if (ret == 0) { if (ret == 0) {
int i; int i;
......
...@@ -209,8 +209,6 @@ extern void *set_except_vector(int n, void *addr); ...@@ -209,8 +209,6 @@ extern void *set_except_vector(int n, void *addr);
extern unsigned long ebase; extern unsigned long ebase;
extern void per_cpu_trap_init(void); extern void per_cpu_trap_init(void);
extern int stop_a_enabled;
/* /*
* See include/asm-ia64/system.h; prevents deadlock on SMP * See include/asm-ia64/system.h; prevents deadlock on SMP
* systems. * systems.
......
...@@ -437,7 +437,7 @@ uart_handle_sysrq_char(struct uart_port *port, unsigned int ch) ...@@ -437,7 +437,7 @@ uart_handle_sysrq_char(struct uart_port *port, unsigned int ch)
#ifdef SUPPORT_SYSRQ #ifdef SUPPORT_SYSRQ
if (port->sysrq) { if (port->sysrq) {
if (ch && time_before(jiffies, port->sysrq)) { if (ch && time_before(jiffies, port->sysrq)) {
handle_sysrq(ch, port->info->tty); handle_sysrq(ch, port->info ? port->info->tty : NULL);
port->sysrq = 0; port->sysrq = 0;
return 1; return 1;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment