[RFC PATCH v3] tty: pl011: Avoid spuriously stuck-off interrupts

Andrew Jones drjones at redhat.com
Wed Apr 25 07:59:28 PDT 2018


On Wed, Apr 25, 2018 at 03:10:14PM +0100, Dave Martin wrote:
> On Wed, Apr 25, 2018 at 01:47:42PM +0100, Ciro Santilli wrote:
> > On Wed, Apr 25, 2018 at 1:20 PM, Andrew Jones <drjones at redhat.com> wrote:
> > > On Mon, Apr 23, 2018 at 02:49:30PM +0100, Peter Maydell wrote:
> > >> On 23 April 2018 at 14:41, Dave Martin <Dave.Martin at arm.com> wrote:
> > >> > This is an update to a previous RFC v2 [1], to fix a problem observed by
> > >> > the qemu community that causes serial input to hang when booting a
> > >> > simulated system with data already queued in the UART FIFO [2].
> > >> >
> > >> > After discussion, I decided that the approach in [1] was over-
> > >> > engineered: it tries to preserve a guarantee that people shouldn't be
> > >> > relying on anyway, namely that data sent to the UART prior to kernel
> > >> > boot will be received by the kernel; or more generally that data
> > >> > received by the UART while the pl011 driver is not opened will be
> > >> > received (either intact or at all) by the driver.
> > >> >
> > >> > If anyone can please test the following and let me know the results,
> > >> > that would be much appreciated!
> > >> >
> > >> >  a) Check that you can still reproduce the bug on mainline without this
> > >> >     patch.
> > >> >
> > >> >  b) Check whether this patch fixes the problem.
> > >>
> > >> Thanks. I'm cc'ing Ciro and Drew, who are the two people I
> > >> recall reporting this issue to me.
> > >> Link to the patch for their benefit:
> > >>  http://lists.infradead.org/pipermail/linux-arm-kernel/2018-April/573120.html
> > >>
> > >
> > > Hi Dave,
> > >
> > > v3 does not resolve the issue for me. v2 does, and, fwiw, v3 applied on
> > > top of v2 (i.e. applying both), still works.
> > >
> > 
> > I also confirm that v2 + v3 on top of Linux kernel v4.16, QEMU v2.11.0
> > solves the problem on arm and aarch64, otherwise if I hit Ctrl + C
> > during boot my terminal become irresponsive after boot. Test setup:
> > https://github.com/cirosantilli/linux-kernel-module-cheat/tree/14965a40d27c8d9d1ff5b023ace827b288a024ef
> 
> Hmmm, interesting.
> 
> Looking at the code, it looks like RXIS is explicitly cleared twice,
> once in pl011_hwinit() and once in pl011_startup().  The CONFIG_POLL
> code uses hwinit alone, and I'm not sure exactly what properties it
> relies on.
> 
> To be most robust, perhaps we should drain the RX FIFO in both places.
> 
> Can you try applying this on top of the branch and see what happens?
> 
> Cheers
> ---Dave
> 
> 
> diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c
> index 73e6f44..841afbd 100644
> --- a/drivers/tty/serial/amba-pl011.c
> +++ b/drivers/tty/serial/amba-pl011.c
> @@ -1652,6 +1652,19 @@ static void pl011_put_poll_char(struct uart_port *port,
>  
>  #endif /* CONFIG_CONSOLE_POLL */
>  
> +/*
> + * RXIS is asserted only when the RX FIFO transitions from below to
> + * above the trigger threshold.  If the RX FIFO is already full to the
> + * threshold this can't happen and RXIS will now be stuck off.
> + * Unless polling the UART, use this function to drain the RX FIFO
> + * explicitly after clearing RXIS.
> + */
> +static void pl011_drain_rx_fifo(struct uart_amba_port *uap)
> +{
> +	while (!(pl011_read(uap, REG_FR) & UART01x_FR_RXFE))
> +		pl011_read(uap, REG_DR);
> +}
> +
>  static int pl011_hwinit(struct uart_port *port)
>  {
>  	struct uart_amba_port *uap =
> @@ -1674,15 +1687,7 @@ static int pl011_hwinit(struct uart_port *port)
>  	pl011_write(UART011_OEIS | UART011_BEIS | UART011_PEIS |
>  		    UART011_FEIS | UART011_RTIS | UART011_RXIS,
>  		    uap, REG_ICR);
> -
> -	/*
> -	 * RXIS is asserted only when the RX FIFO transitions from below
> -	 * to above the trigger threshold.  If the RX FIFO is already
> -	 * full to the threshold this can't happen and RXIS will now be
> -	 * stuck off.  Drain the FIFO explicitly to fix this:
> -	 */
> -	while (!(pl011_read(uap, REG_FR) & UART01x_FR_RXFE))
> -		pl011_read(uap, REG_DR);
> +	pl011_drain_rx_fifo(uap);
>  
>  	/*
>  	 * Save interrupts enable mask, and enable RX interrupts in case if
> @@ -1740,6 +1745,8 @@ static void pl011_enable_interrupts(struct uart_amba_port *uap)
>  
>  	/* Clear out any spuriously appearing RX interrupts */
>  	pl011_write(UART011_RTIS | UART011_RXIS, uap, REG_ICR);
> +	pl011_drain_rx_fifo(uap);
> +
>  	uap->im = UART011_RTIM;
>  	if (!pl011_dma_rx_running(uap))
>  		uap->im |= UART011_RXIM;
> 
> 
>

This worked for me. To be clear, I applied the following, and nothing
else, to my base kernel for the test

diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c
index 111e6a9..d64b64f 100644
--- a/drivers/tty/serial/amba-pl011.c
+++ b/drivers/tty/serial/amba-pl011.c
@@ -1672,6 +1672,19 @@ static void pl011_put_poll_char(struct uart_port *port,
 
 #endif /* CONFIG_CONSOLE_POLL */
 
+/*
+ * RXIS is asserted only when the RX FIFO transitions from below to
+ * above the trigger threshold.  If the RX FIFO is already full to the
+ * threshold this can't happen and RXIS will now be stuck off.
+ * Unless polling the UART, use this function to drain the RX FIFO
+ * explicitly after clearing RXIS.
+ */
+static void pl011_drain_rx_fifo(struct uart_amba_port *uap)
+{
+       while (!(pl011_read(uap, REG_FR) & UART01x_FR_RXFE))
+               pl011_read(uap, REG_DR);
+}
+
 static int pl011_hwinit(struct uart_port *port)
 {
        struct uart_amba_port *uap =
@@ -1695,6 +1708,8 @@ static int pl011_hwinit(struct uart_port *port)
                    UART011_FEIS | UART011_RTIS | UART011_RXIS,
                    uap, REG_ICR);
 
+       pl011_drain_rx_fifo(uap);
+
        /*
         * Save interrupts enable mask, and enable RX interrupts in case if
         * the interrupt is used for NMI entry.
@@ -1751,6 +1766,8 @@ static void pl011_enable_interrupts(struct uart_amba_port *uap)
 
        /* Clear out any spuriously appearing RX interrupts */
        pl011_write(UART011_RTIS | UART011_RXIS, uap, REG_ICR);
+       pl011_drain_rx_fifo(uap);
+
        uap->im = UART011_RTIM;
        if (!pl011_dma_rx_running(uap))
                uap->im |= UART011_RXIM;


Thanks,
drew 



More information about the linux-arm-kernel mailing list