sdio: fix recursion issues between sdio-uart driver and tty layer

In a few places, sdio_uart_irq() is called directly instead of waiting
for the actual interrupt to be raised and the SDIO IRQ thread scheduled
in order to reduce latency.  However, some interaction with the tty core
may end up calling us back (serial echo, flow control, etc.) creating
two issues:

 - the host lock gets claimed twice from the same thread causing a
   deadlock;

 - the same direct calls to sdio_uart_irq() may be performed causing
   unexpected reentrancy into the IRQ handler.

This patch handles both of those issues.

Signed-off-by: Nicolas Pitre <npitre@mvista.com>
Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
diff --git a/drivers/mmc/card/sdio_uart.c b/drivers/mmc/card/sdio_uart.c
index 6061002..d552de6 100644
--- a/drivers/mmc/card/sdio_uart.c
+++ b/drivers/mmc/card/sdio_uart.c
@@ -79,6 +79,7 @@
 	struct mutex		open_lock;
 	struct sdio_func	*func;
 	struct mutex		func_lock;
+	struct task_struct	*in_sdio_uart_irq;
 	unsigned int		regs_offset;
 	struct circ_buf		xmit;
 	spinlock_t		write_lock;
@@ -186,14 +187,16 @@
 		mutex_unlock(&port->func_lock);
 		return -ENODEV;
 	}
-	sdio_claim_host(port->func);
+	if (likely(port->in_sdio_uart_irq != current))
+		sdio_claim_host(port->func);
 	mutex_unlock(&port->func_lock);
 	return 0;
 }
 
 static inline void sdio_uart_release_func(struct sdio_uart_port *port)
 {
-	sdio_release_host(port->func);
+	if (likely(port->in_sdio_uart_irq != current))
+		sdio_release_host(port->func);
 }
 
 static inline unsigned int sdio_in(struct sdio_uart_port *port, int offset)
@@ -511,15 +514,29 @@
 	struct sdio_uart_port *port = sdio_get_drvdata(func);
 	unsigned int iir, lsr;
 
+	/*
+	 * In a few places sdio_uart_irq() is called directly instead of
+	 * waiting for the actual interrupt to be raised and the SDIO IRQ
+	 * thread scheduled in order to reduce latency.  However, some
+	 * interaction with the tty core may end up calling us back
+	 * (serial echo, flow control, etc.) through those same places
+	 * causing undesirable effects.  Let's stop the recursion here.
+	 */
+	if (unlikely(port->in_sdio_uart_irq == current))
+		return;
+
 	iir = sdio_in(port, UART_IIR);
 	if (iir & UART_IIR_NO_INT)
 		return;
+
+	port->in_sdio_uart_irq = current;
 	lsr = sdio_in(port, UART_LSR);
 	if (lsr & UART_LSR_DR)
 		sdio_uart_receive_chars(port, &lsr);
 	sdio_uart_check_modem_status(port);
 	if (lsr & UART_LSR_THRE)
 		sdio_uart_transmit_chars(port);
+	port->in_sdio_uart_irq = NULL;
 }
 
 static int sdio_uart_startup(struct sdio_uart_port *port)