[PATCH 2/2] SERIAL_CONSOLE: Add support for running console rx interrupts directly

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch allows serial console interrupts to be optionally handled in
interrupt context directly instead of dispatching a thread.  It is only
applicable when IRQs are threaded, and is thus predicated on PREEMPT_HARDIRQS.

This modification can be useful for allowing tools like sysrq to get
through more reliably.

Signed-off-by: Gregory Haskins <[email protected]>
---

 drivers/char/sysrq.c        |    8 +
 drivers/serial/8250.c       |  239 ++++++++++++++++++++++++++++++++++---------
 drivers/serial/8250.h       |    6 +
 drivers/serial/Kconfig      |   16 +++
 include/linux/serial_core.h |    8 +
 5 files changed, 223 insertions(+), 54 deletions(-)

diff --git a/drivers/char/sysrq.c b/drivers/char/sysrq.c
index 37d670d..40d6fcf 100644
--- a/drivers/char/sysrq.c
+++ b/drivers/char/sysrq.c
@@ -325,8 +325,14 @@ static struct sysrq_key_op sysrq_unrt_op = {
 	.enable_mask	= SYSRQ_ENABLE_RTNICE,
 };
 
+#ifdef CONFIG_SERIAL_8250_CONSOLE_RAWIRQ
+#define DEFINE_SYSRQ_SPINLOCK DEFINE_RAW_SPINLOCK
+#else
+#define DEFINE_SYSRQ_SPINLOCK DEFINE_SPINLOCK
+#endif
+
 /* Key Operations table and lock */
-static DEFINE_SPINLOCK(sysrq_key_table_lock);
+static DEFINE_SYSRQ_SPINLOCK(sysrq_key_table_lock);
 
 static struct sysrq_key_op *sysrq_key_table[36] = {
 	&sysrq_loglevel_op,		/* 0 */
diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c
index e443098..85af4d2 100644
--- a/drivers/serial/8250.c
+++ b/drivers/serial/8250.c
@@ -53,6 +53,8 @@
  */
 static unsigned int share_irqs = SERIAL8250_SHARE_IRQS;
 
+static unsigned int raw_irqs = SERIAL_8250_CONSOLE_RAWIRQ;
+
 static unsigned int nr_uarts = CONFIG_SERIAL_8250_RUNTIME_UARTS;
 
 /*
@@ -116,6 +118,21 @@ static unsigned long probe_rsa[PORT_RSA_MAX];
 static unsigned int probe_rsa_count;
 #endif /* CONFIG_SERIAL_8250_RSA  */
 
+struct uart_8250_char {
+	unsigned int status;
+	unsigned int overrun;
+	unsigned int ch;
+};
+
+#define SERIAL8250_RINGSIZE 1024
+
+struct uart_8250_ring {
+	unsigned int          head;
+	unsigned int          tail;
+	unsigned int          count;
+	struct uart_8250_char data[SERIAL8250_RINGSIZE];
+};
+
 struct uart_8250_port {
 	struct uart_port	port;
 	struct timer_list	timer;		/* "no irq" timer */
@@ -145,10 +162,21 @@ struct uart_8250_port {
 	 */
 	void			(*pm)(struct uart_port *port,
 				      unsigned int state, unsigned int old);
+
+	void                    (*rx_char)(struct uart_8250_port *up,
+					   unsigned int status,
+					   unsigned int overrun,
+					   unsigned int ch);
+	void                    (*rx_kick)(struct uart_8250_port *up);
+
+#ifdef CONFIG_SERIAL_8250_CONSOLE_RAWIRQ
+	struct tasklet_struct   rx_task;
+	struct uart_8250_ring   ring;
+#endif
 };
 
 struct irq_info {
-	spinlock_t		lock;
+	uart_spinlock_t		lock;
 	struct list_head	*head;
 };
 
@@ -1311,6 +1339,137 @@ static void serial8250_enable_ms(struct uart_port *port)
 }
 
 static void
+serial8250_direct_rx_char(struct uart_8250_port *up, unsigned int lsr,
+			  unsigned int overrun, unsigned int ch)
+{
+	char flag = TTY_NORMAL;
+	char xmit = 1;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&up->port.lock, flags);
+	
+	up->port.icount.rx++;
+	
+#ifdef CONFIG_SERIAL_8250_CONSOLE
+	/*
+	 * Recover the break flag from console xmit
+	 */
+	if (up->port.line == up->port.cons->index) {
+		lsr |= up->lsr_break_flag;
+		up->lsr_break_flag = 0;
+	}
+#endif
+	
+	if (unlikely(lsr & (UART_LSR_BI | UART_LSR_PE |
+			    UART_LSR_FE | UART_LSR_OE))) {
+		/*
+		 * For statistics only
+		 */
+		if (lsr & UART_LSR_BI) {
+			lsr &= ~(UART_LSR_FE | UART_LSR_PE);
+			up->port.icount.brk++;
+			/*
+			 * We do the SysRQ and SAK checking
+			 * here because otherwise the break
+			 * may get masked by ignore_status_mask
+			 * or read_status_mask.
+			 */
+			if (uart_handle_break(&up->port))
+				xmit = 0;
+		} else if (lsr & UART_LSR_PE)
+			up->port.icount.parity++;
+		else if (lsr & UART_LSR_FE)
+			up->port.icount.frame++;
+		if (lsr & UART_LSR_OE)
+			up->port.icount.overrun++;
+		
+		/*
+		 * Mask off conditions which should be ignored.
+		 */
+		lsr &= up->port.read_status_mask;
+		
+		if (lsr & UART_LSR_BI) {
+			DEBUG_INTR("handling break....");
+			flag = TTY_BREAK;
+		} else if (lsr & UART_LSR_PE)
+			flag = TTY_PARITY;
+		else if (lsr & UART_LSR_FE)
+			flag = TTY_FRAME;
+	}
+	
+	spin_unlock_irqrestore(&up->port.lock, flags);
+	
+	if (xmit)
+		uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag);
+}
+
+static void
+serial8250_direct_rx_kick(struct uart_8250_port *up)
+{
+	tty_flip_buffer_push(up->port.info->tty);
+}
+
+#ifdef CONFIG_SERIAL_8250_CONSOLE_RAWIRQ
+static void raw_rx_handler(unsigned long data)
+{
+	struct uart_8250_port *up   = (struct uart_8250_port *)data;
+	struct uart_8250_ring *ring = &up->ring;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&up->port.lock, flags);
+	
+	while(ring->count) {
+		struct uart_8250_char c = ring->data[ring->tail];
+		
+		ring->tail++;
+		ring->tail %= SERIAL8250_RINGSIZE;
+		ring->count--;
+		
+		spin_unlock_irqrestore(&up->port.lock, flags);
+		
+		serial8250_direct_rx_char(up, c.status, c.overrun, c.ch);
+		
+		spin_lock_irqsave(&up->port.lock, flags);
+	}
+	
+	spin_unlock_irqrestore(&up->port.lock, flags);
+	
+	serial8250_direct_rx_kick(up);
+}
+
+static void
+serial8250_raw_rx_char(struct uart_8250_port *up, unsigned int status,
+		       unsigned int overrun, unsigned int ch)
+{
+	struct uart_8250_ring *ring = &up->ring;
+	struct uart_8250_char *c;
+	
+	spin_lock_bh(&up->port.lock);
+	
+	if (ring->count == SERIAL8250_RINGSIZE)
+		goto out;
+	
+	c = &ring->data[ring->head];
+	c->status  = status;
+	c->overrun = overrun;
+	c->ch      = ch;
+	
+	ring->head++;
+	ring->head %= SERIAL8250_RINGSIZE;
+	ring->count++;
+	
+ out:
+	spin_unlock_bh(&up->port.lock);
+}
+
+static void
+serial8250_raw_rx_kick(struct uart_8250_port *up)
+{
+	tasklet_schedule(&up->rx_task);
+}
+#endif
+
+static void
 receive_chars(struct uart_8250_port *up, unsigned int *status)
 {
 	struct tty_struct *tty = up->port.info->tty;
@@ -1319,59 +1478,19 @@ receive_chars(struct uart_8250_port *up, unsigned int *status)
 	char flag;
 
 	do {
-		ch = __serial_inp(up, UART_RX);
-		flag = TTY_NORMAL;
-		up->port.icount.rx++;
-
-		lsr |= up->lsr_saved_flags;
-		up->lsr_saved_flags = 0;
-
-		if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) {
-			/*
-			 * For statistics only
-			 */
-			if (lsr & UART_LSR_BI) {
-				lsr &= ~(UART_LSR_FE | UART_LSR_PE);
-				up->port.icount.brk++;
-				/*
-				 * We do the SysRQ and SAK checking
-				 * here because otherwise the break
-				 * may get masked by ignore_status_mask
-				 * or read_status_mask.
-				 */
-				if (uart_handle_break(&up->port))
-					goto ignore_char;
-			} else if (lsr & UART_LSR_PE)
-				up->port.icount.parity++;
-			else if (lsr & UART_LSR_FE)
-				up->port.icount.frame++;
-			if (lsr & UART_LSR_OE)
-				up->port.icount.overrun++;
+		ch = serial_inp(up, UART_RX);
 
-			/*
-			 * Mask off conditions which should be ignored.
-			 */
-			lsr &= up->port.read_status_mask;
-
-			if (lsr & UART_LSR_BI) {
-				DEBUG_INTR("handling break....");
-				flag = TTY_BREAK;
-			} else if (lsr & UART_LSR_PE)
-				flag = TTY_PARITY;
-			else if (lsr & UART_LSR_FE)
-				flag = TTY_FRAME;
-		}
 		if (uart_handle_sysrq_char(&up->port, ch))
 			goto ignore_char;
 
-		uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag);
+		up->rx_char(up, lsr, UART_LSR_OE, ch);
 
 	ignore_char:
-		lsr = __serial_inp(up, UART_LSR);
+		lsr = serial_inp(up, UART_LSR);
 	} while ((lsr & UART_LSR_DR) && (max_count-- > 0));
-	spin_unlock(&up->port.lock);
-	tty_flip_buffer_push(tty);
-	spin_lock(&up->port.lock);
+
+	up->rx_kick(up);
+
 	*status = lsr;
 }
 
@@ -1445,14 +1564,15 @@ serial8250_handle_port(struct uart_8250_port *up)
 	unsigned int status;
 	unsigned long flags;
 
-	spin_lock_irqsave(&up->port.lock, flags);
-
-	status = __serial_inp(up, UART_LSR);
+	status = serial_inp(up, UART_LSR);
 
 	DEBUG_INTR("status = %x...", status);
 
 	if (status & UART_LSR_DR)
 		receive_chars(up, &status);
+
+	spin_lock_irqsave(&up->port.lock, flags);
+
 	check_modem_status(up);
 	if (status & UART_LSR_THRE)
 		transmit_chars(up);
@@ -1568,6 +1688,9 @@ static int serial_link_irq_chain(struct uart_8250_port *up)
 	struct irq_info *i = irq_lists + up->port.irq;
 	int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0;
 
+	if (raw_irqs)
+		irq_flags |= IRQF_NODELAY;
+
 	spin_lock_irq(&i->lock);
 
 	if (i->head) {
@@ -2851,6 +2974,18 @@ int serial8250_register_port(struct uart_port *port)
 		if (port->dev)
 			uart->port.dev = port->dev;
 
+#ifdef CONFIG_SERIAL_8250_CONSOLE_RAWIRQ
+		tasklet_init(&uart->rx_task, raw_rx_handler,
+			     (unsigned long)uart);
+		memset(&uart->ring, 0, sizeof(uart->ring));
+		uart->rx_char = serial8250_raw_rx_char;
+		uart->rx_kick = serial8250_raw_rx_kick;
+#else
+		uart->rx_char = serial8250_direct_rx_char;
+		uart->rx_kick = serial8250_direct_rx_kick;
+
+#endif
+
 		ret = uart_add_one_port(&serial8250_reg, &uart->port);
 		if (ret == 0)
 			ret = uart->port.line;
@@ -2894,8 +3029,8 @@ static int __init serial8250_init(void)
 		nr_uarts = UART_NR;
 
 	printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
-		"%d ports, IRQ sharing %sabled\n", nr_uarts,
-		share_irqs ? "en" : "dis");
+		"%d ports, IRQ: sharing=%sabled, mode=%s\n", nr_uarts,
+		share_irqs ? "en" : "dis", raw_irqs ? "raw" : "normal");
 
 	for (i = 0; i < NR_IRQS; i++)
 		spin_lock_init(&irq_lists[i].lock);
diff --git a/drivers/serial/8250.h b/drivers/serial/8250.h
index 91bd28f..2f5aacf 100644
--- a/drivers/serial/8250.h
+++ b/drivers/serial/8250.h
@@ -61,6 +61,12 @@ struct serial8250_config {
 #define SERIAL8250_SHARE_IRQS 0
 #endif
 
+#ifdef CONFIG_SERIAL_8250_CONSOLE_RAWIRQ
+#define SERIAL_8250_CONSOLE_RAWIRQ 1
+#else
+#define SERIAL_8250_CONSOLE_RAWIRQ 0
+#endif
+
 #if defined(__alpha__) && !defined(CONFIG_PCI)
 /*
  * Digital did something really horribly wrong with the OUT1 and OUT2
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 81b52b7..3f095f2 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -78,6 +78,22 @@ config FIX_EARLYCON_MEM
 	depends on X86
 	default y
 
+config SERIAL_8250_CONSOLE_RAWIRQ
+       bool "Disable serial console IRQ threading"
+       depends on SERIAL_8250_CONSOLE=y && PREEMPT_HARDIRQS
+       default n
+       ---help---
+        If you say Y here, serial console interrupts will be handled in
+        interrupt context directly instead of dispatching a thread.  This can
+        be useful for allowing tools like sysrq to get through more reliably.
+
+        Enabling this option may have a detrimental effect on latency
+        sensitive workloads.  It should only be used when sysrq is needed to
+        break into systems where threaded-irqs are not being serviced in a
+        timely manner.
+
+        If unsure, say N
+
 config SERIAL_8250_GSC
 	tristate
 	depends on SERIAL_8250 && GSC
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 09d17b0..b2b9d43 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -225,8 +225,14 @@ struct uart_icount {
 
 typedef unsigned int __bitwise__ upf_t;
 
+#ifdef CONFIG_SERIAL_8250_CONSOLE_RAWIRQ
+typedef raw_spinlock_t uart_spinlock_t;
+#else
+typedef spinlock_t uart_spinlock_t;
+#endif
+
 struct uart_port {
-	spinlock_t		lock;			/* port lock */
+	uart_spinlock_t		lock;			/* port lock */
 	unsigned int		iobase;			/* in/out[bwl] */
 	unsigned char __iomem	*membase;		/* read/write[bwl] */
 	unsigned int		irq;			/* irq number */

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux