Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > 27922b4260f65d317aabda37e42bbbff > files > 2098

kernel-2.6.18-238.el5.src.rpm

From: Doug Chapman <dchapman@redhat.com>
Date: Fri, 10 Aug 2007 17:49:18 -0400
Subject: [misc] serial: fix console hang on HP Integrity
Message-id: 1186782558.3382.12.camel@deimos.americas.hpqcorp.net
O-Subject: [RHEL5.2 patch] fix for serial console hang on HP Integrity
Bugzilla: 244054

Fix for BZ 244054

The management processor UARTs on the low-end IPF systems have an issue
with "losing" interrupts.  A normal 16550 UART reasserts the THRE interrupt
whenever the transmitter is empty and the IIR THRI bit is re-enabled.  The
MP UART does not reassert this interrupt and can therefore get into a hung
console state.  Pressing a key on the console will kick the UART into running
again, but the hiccup is an issue for unattended reboots.

This patch was recently pulled into RHEL4.6 and was submitted
upstream here:

http://lkml.org/lkml/2006/6/27/47 2

Tested on several HP Integrity systems as well as some HP x86 systems with
serial console.

- Doug

Acked-by: Pete Zaitcev <zaitcev@redhat.com>
Acked-by: Alan Cox <alan@redhat.com>
---
 drivers/serial/8250.c |  168 ++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 131 insertions(+), 37 deletions(-)

diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c
index 403efad..f7c9f23 100644
--- a/drivers/serial/8250.c
+++ b/drivers/serial/8250.c
@@ -367,6 +367,19 @@ serial_out(struct uart_8250_port *up, int offset, int value)
 	}
 }
 
+static void
+serial_out_sync(struct uart_8250_port *up, int offset, int value)
+{
+	switch (up->port.iotype) {
+	case SERIAL_IO_MEM:
+		serial_out(up, offset, value);
+		(void)serial_in(up, UART_LCR); /* safe, no side-effects */
+		break;
+	default:
+		serial_out(up, offset, value);
+	}
+}
+
 /*
  * We used to support using pause I/O for certain machines.  We
  * haven't supported this for a while, but just in case it's badly
@@ -1316,8 +1329,9 @@ static inline void
 serial8250_handle_port(struct uart_8250_port *up, struct pt_regs *regs)
 {
 	unsigned int status;
+	unsigned long flags;
 
-	spin_lock(&up->port.lock);
+	spin_lock_irqsave(&up->port.lock, flags);
 
 	status = serial_inp(up, UART_LSR);
 
@@ -1329,7 +1343,7 @@ serial8250_handle_port(struct uart_8250_port *up, struct pt_regs *regs)
 	if (status & UART_LSR_THRE)
 		transmit_chars(up);
 
-	spin_unlock(&up->port.lock);
+	spin_unlock_irqrestore(&up->port.lock, flags);
 }
 
 /*
@@ -1472,6 +1486,48 @@ static void serial8250_timeout(unsigned long data)
 	mod_timer(&up->timer, jiffies + timeout);
 }
 
+static void serial8250_backup_timeout(unsigned long data)
+{
+	struct uart_8250_port *up = (struct uart_8250_port *)data;
+	unsigned int iir, ier = 0;
+	unsigned int timeout = up->port.timeout;
+
+	/*
+	 * Must disable interrupts or else we risk triggering an interrupt
+	 * and getting dead locked by the port.lock.
+	 */
+	if (is_real_interrupt(up->port.irq)) {
+		ier = serial_in(up, UART_IER);
+		serial_out(up, UART_IER, 0);
+	}
+
+	iir = serial_in(up, UART_IIR);
+
+	/*
+	 * This should be a safe test for anyone who doesn't trust the
+	 * IIR bits on their UART, but it's specifically designed for
+	 * the "Diva" UART used on the management processor on many HP
+	 * ia64 and parisc boxes.
+	 */
+	if ((iir & UART_IIR_NO_INT) && (up->ier & UART_IER_THRI) &&
+	    (!uart_circ_empty(&up->port.info->xmit) || up->port.x_char) &&
+	    (serial_in(up, UART_LSR) & UART_LSR_THRE)) {
+		iir &= ~(UART_IIR_ID | UART_IIR_NO_INT);
+		iir |= UART_IIR_THRI;
+	}
+
+	if (!(iir & UART_IIR_NO_INT)) {
+		serial8250_handle_port(up, NULL);
+	}
+
+	if (is_real_interrupt(up->port.irq))
+		serial_out(up, UART_IER, ier);
+
+	timeout = timeout > 6 ? (timeout / 2 - 2) : 1;
+
+	mod_timer(&up->timer, jiffies + timeout + HZ/5);
+}
+
 static unsigned int serial8250_tx_empty(struct uart_port *port)
 {
 	struct uart_8250_port *up = (struct uart_8250_port *)port;
@@ -1540,6 +1596,37 @@ static void serial8250_break_ctl(struct uart_port *port, int break_state)
 	spin_unlock_irqrestore(&up->port.lock, flags);
 }
 
+#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
+
+/*
+ *	Wait for transmitter & holding register to empty
+ */
+static inline void wait_for_xmitr(struct uart_8250_port *up, int bits)
+{
+	unsigned int status, tmout = 10000;
+
+	/* Wait up to 10ms for the character(s) to be sent. */
+	do {
+		status = serial_in(up, UART_LSR);
+
+		if (status & UART_LSR_BI)
+			up->lsr_break_flag = UART_LSR_BI;
+
+		if (--tmout == 0)
+			break;
+		udelay(1);
+	} while ((status & bits) != bits);
+
+	/* Wait up to 1s for flow control if necessary */
+	if (up->port.flags & UPF_CONS_FLOW) {
+		tmout = 1000000;
+		while (!(serial_in(up, UART_MSR) & UART_MSR_CTS) && --tmout) {
+			udelay(1);
+			touch_nmi_watchdog();
+		}
+	}
+}
+
 static int serial8250_startup(struct uart_port *port)
 {
 	struct uart_8250_port *up = (struct uart_8250_port *)port;
@@ -1613,6 +1700,45 @@ static int serial8250_startup(struct uart_port *port)
 		serial_outp(up, UART_LCR, 0);
 	}
 
+	if (is_real_interrupt(up->port.irq)) {
+		unsigned int iir;
+
+		/*
+		 * Test for UARTs that do not reassert THRE when the
+		 * transmitter is idle and the interrupt has already
+		 * been cleared.  Real 16550s should always reassert
+		 * this interrupt whenever the transmitter is idle and
+		 * the interrupt is enabled.  Delays are necessary to
+		 * allow register changes to become visible.
+		 */
+		spin_lock_irqsave(&up->port.lock, flags);
+
+		wait_for_xmitr(up, UART_LSR_THRE);
+		serial_out_sync(up, UART_IER, UART_IER_THRI);
+		udelay(1); /* allow THRE to set */
+		serial_in(up, UART_IIR);
+		serial_out(up, UART_IER, 0);
+		serial_out_sync(up, UART_IER, UART_IER_THRI);
+		udelay(1); /* allow a working UART time to re-assert THRE */
+		iir = serial_in(up, UART_IIR);
+		serial_out(up, UART_IER, 0);
+
+		spin_unlock_irqrestore(&up->port.lock, flags);
+
+		/*
+		 * If the interrupt is not reasserted, setup a timer to
+		 * kick the UART on a regular basis.
+		 */
+		if (iir & UART_IIR_NO_INT) {
+			unsigned int timeout = up->port.timeout;
+
+			timeout = timeout > 6 ? (timeout / 2 - 2) : 1;
+			up->timer.function = serial8250_backup_timeout;
+			up->timer.data = (unsigned long)up;
+			mod_timer(&up->timer, jiffies + timeout + HZ/5);
+		}
+	}
+
 	/*
 	 * If the "interrupt" for this port doesn't correspond with any
 	 * hardware interrupt, we use a timer-based system.  The original
@@ -1740,9 +1866,9 @@ static void serial8250_shutdown(struct uart_port *port)
 	 */
 	(void) serial_in(up, UART_RX);
 
-	if (!is_real_interrupt(up->port.irq))
-		del_timer_sync(&up->timer);
-	else
+	del_timer_sync(&up->timer);
+	up->timer.function = serial8250_timeout;
+	if (is_real_interrupt(up->port.irq))
 		serial_unlink_irq_chain(up);
 }
 
@@ -2224,38 +2350,6 @@ serial8250_register_ports(struct uart_driver *drv, struct device *dev)
 
 #ifdef CONFIG_SERIAL_8250_CONSOLE
 
-#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
-
-/*
- *	Wait for transmitter & holding register to empty
- */
-static inline void wait_for_xmitr(struct uart_8250_port *up, int bits)
-{
-	unsigned int status, tmout = 10000;
-
-	/* Wait up to 10ms for the character(s) to be sent. */
-	do {
-		status = serial_in(up, UART_LSR);
-
-		if (status & UART_LSR_BI)
-			up->lsr_break_flag = UART_LSR_BI;
-
-		if (--tmout == 0)
-			break;
-		udelay(1);
-	} while ((status & bits) != bits);
-
-	/* Wait up to 1s for flow control if necessary */
-	if (up->port.flags & UPF_CONS_FLOW) {
-		tmout = 1000000;
-		while (!(serial_in(up, UART_MSR) & UART_MSR_CTS) && --tmout) {
-			udelay(1);
-			if ((tmout % 1000) == 0)
-				touch_nmi_watchdog();
-		}
-	}
-}
-
 static void serial8250_console_putchar(struct uart_port *port, int ch)
 {
 	struct uart_8250_port *up = (struct uart_8250_port *)port;
-- 
1.5.3.5.645.gbb47