diff mbox series

[RFC,INTERNAL,v2,2/4] serial: core: Add framework to allow NMI aware serial drivers

Message ID 1594386725-10346-3-git-send-email-sumit.garg@linaro.org
State New
Headers show
Series Introduce NMI aware serial drivers | expand

Commit Message

Sumit Garg July 10, 2020, 1:12 p.m. UTC
With the advent of pseudo NMIs on arm64 platforms, its been now possible
to have NMI driven serial drivers which enables us to have magic sysrq
running in NMI context that could be helpful to debug hardlockup
scenarios especially via enabling kernel debugger to run in NMI context.

So add corresponding NMI helper APIs that can be leveraged by serial
drivers. Also, make sysrq handler APIs NMI safe.

Signed-off-by: Sumit Garg <sumit.garg@linaro.org>

---
 drivers/tty/serial/serial_core.c | 97 +++++++++++++++++++++++++++++++++++++++-
 include/linux/serial_core.h      | 43 ++++++++++++++++++
 2 files changed, 138 insertions(+), 2 deletions(-)

-- 
2.7.4

Comments

Daniel Thompson July 13, 2020, 2:18 p.m. UTC | #1
On Fri, Jul 10, 2020 at 06:42:03PM +0530, Sumit Garg wrote:
> With the advent of pseudo NMIs on arm64 platforms, its been now possible

> to have NMI driven serial drivers which enables us to have magic sysrq

> running in NMI context that could be helpful to debug hardlockup

> scenarios especially via enabling kernel debugger to run in NMI context.

> 

> So add corresponding NMI helper APIs that can be leveraged by serial

> drivers. Also, make sysrq handler APIs NMI safe.

> 

> Signed-off-by: Sumit Garg <sumit.garg@linaro.org>

> ---

>  drivers/tty/serial/serial_core.c | 97 +++++++++++++++++++++++++++++++++++++++-

>  include/linux/serial_core.h      | 43 ++++++++++++++++++

>  2 files changed, 138 insertions(+), 2 deletions(-)

> 

> diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c

> index 57840cf..a59d7ff 100644

> --- a/drivers/tty/serial/serial_core.c

> +++ b/drivers/tty/serial/serial_core.c

> @@ -3181,16 +3181,28 @@ static bool uart_try_toggle_sysrq(struct uart_port *port, unsigned int ch)

>  		return true;

>  	}

>  

> -	schedule_work(&sysrq_enable_work);

> +	if (in_nmi())

> +		irq_work_queue(&port->nmi_state.sysrq_toggle_work);

> +	else

> +		schedule_work(&sysrq_enable_work);

>  

>  	port->sysrq = 0;

>  	return true;

>  }

> +

> +static void uart_nmi_toggle_work(struct irq_work *work)

> +{

> +	schedule_work(&sysrq_enable_work);

> +}

>  #else

>  static inline bool uart_try_toggle_sysrq(struct uart_port *port, unsigned int ch)

>  {

>  	return false;

>  }

> +

> +static void uart_nmi_toggle_work(struct irq_work *work)

> +{

> +}

>  #endif

>  

>  int uart_handle_sysrq_char(struct uart_port *port, unsigned int ch)

> @@ -3273,12 +3285,93 @@ int uart_handle_break(struct uart_port *port)

>  		port->sysrq = 0;

>  	}

>  

> -	if (port->flags & UPF_SAK)

> +	if (in_nmi() && (port->flags & UPF_SAK))

> +		irq_work_queue(&port->nmi_state.sysrq_sak_work);

> +	else if (port->flags & UPF_SAK)

>  		do_SAK(state->port.tty);

>  	return 0;

>  }

>  EXPORT_SYMBOL_GPL(uart_handle_break);

>  

> +static void uart_nmi_sak_work(struct irq_work *work)

> +{

> +	struct uart_nmi_state *nmi_state =

> +	    container_of(work, struct uart_nmi_state, sysrq_sak_work);

> +	struct uart_port *port = nmi_state->port;

> +

> +	do_SAK(port->state->port.tty);

> +}

> +

> +static void uart_nmi_rx_work(struct irq_work *rx_work)

> +{

> +	struct uart_nmi_state *nmi_state =

> +	    container_of(rx_work, struct uart_nmi_state, rx_work);

> +	struct uart_port *port = nmi_state->port;

> +	struct uart_nmi_rx_data rx_data;

> +

> +	/*

> +	 * In polling mode, serial device is initialized much prior to

> +	 * TTY port becoming active. This scenario is especially useful

> +	 * from debugging perspective such that magic sysrq or debugger

> +	 * entry would still be possible even when TTY port isn't

> +	 * active (consider a boot hang case or if a user hasn't opened

> +	 * the serial port). So we discard any other RX data apart from

> +	 * magic sysrq commands in case TTY port isn't active.

> +	 */

> +	if (!port->state || !tty_port_active(&port->state->port)) {

> +		kfifo_reset(&nmi_state->rx_fifo);

> +		return;

> +	}

> +

> +	if (unlikely(!kfifo_len(&nmi_state->rx_fifo)))

> +		return;


If this is unlikely then why bother check it at all?


> +

> +	spin_lock(&port->lock);

> +	while (kfifo_out(&nmi_state->rx_fifo, &rx_data, 1))

> +		uart_insert_char(port, rx_data.ch, rx_data.overrun,

> +				 rx_data.ch, rx_data.flag);

> +	spin_unlock(&port->lock);

> +

> +	tty_flip_buffer_push(&port->state->port);

> +}

> +

> +static void uart_nmi_tx_work(struct irq_work *tx_work)

> +{

> +	struct uart_nmi_state *nmi_state =

> +	    container_of(tx_work, struct uart_nmi_state, tx_work);

> +	struct uart_port *port = nmi_state->port;

> +

> +	spin_lock(&port->lock);

> +	if (nmi_state->tx_irq_callback)

> +		nmi_state->tx_irq_callback(port);

> +	spin_unlock(&port->lock);

> +}

> +

> +void uart_nmi_handle_rx_data(struct uart_port *port,

> +			     struct uart_nmi_rx_data *rx_data)

> +{

> +	WARN_ON_ONCE(!kfifo_in(&port->nmi_state.rx_fifo, rx_data, 1));


This isn't error handling. This is just yelling if an error happens
(which might be OK except that the upper level includes a mechanism to
report overruns when we get too much data on the serial line).


> +}

> +EXPORT_SYMBOL_GPL(uart_nmi_handle_rx_data);

> +

> +int uart_nmi_state_init(struct uart_port *port)

> +{

> +	int ret;

> +

> +	ret = kfifo_alloc(&port->nmi_state.rx_fifo, 256, GFP_KERNEL);

> +	if (ret)

> +		return ret;

> +

> +	port->nmi_state.port = port;

> +	init_irq_work(&port->nmi_state.tx_work, uart_nmi_tx_work);

> +	init_irq_work(&port->nmi_state.rx_work, uart_nmi_rx_work);

> +	init_irq_work(&port->nmi_state.sysrq_sak_work, uart_nmi_sak_work);

> +	init_irq_work(&port->nmi_state.sysrq_toggle_work, uart_nmi_toggle_work);

> +

> +	return ret;

> +}

> +EXPORT_SYMBOL_GPL(uart_nmi_state_init);

> +

>  EXPORT_SYMBOL(uart_write_wakeup);

>  EXPORT_SYMBOL(uart_register_driver);

>  EXPORT_SYMBOL(uart_unregister_driver);

> diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h

> index 9fd550e..51d62f4 100644

> --- a/include/linux/serial_core.h

> +++ b/include/linux/serial_core.h

> @@ -18,6 +18,8 @@

>  #include <linux/tty.h>

>  #include <linux/mutex.h>

>  #include <linux/sysrq.h>

> +#include <linux/irq_work.h>

> +#include <linux/kfifo.h>

>  #include <uapi/linux/serial_core.h>

>  

>  #ifdef CONFIG_SERIAL_CORE_CONSOLE

> @@ -103,6 +105,27 @@ struct uart_icount {

>  typedef unsigned int __bitwise upf_t;

>  typedef unsigned int __bitwise upstat_t;

>  

> +struct uart_nmi_rx_data {

> +	unsigned int		ch;

> +	unsigned int		overrun;

> +	unsigned int		flag;

> +};

> +

> +struct uart_nmi_state {

> +	bool			active;

> +

> +	struct irq_work		tx_work;

> +	void			(*tx_irq_callback)(struct uart_port *port);

> +

> +	struct irq_work		rx_work;

> +	DECLARE_KFIFO_PTR(rx_fifo, struct uart_nmi_rx_data);

> +

> +	struct irq_work		sysrq_sak_work;

> +	struct irq_work		sysrq_toggle_work;

> +

> +	struct uart_port	*port;


This structure is statically allocated... can we use containerof instead
of backlinks?


> +};

> +

>  struct uart_port {

>  	spinlock_t		lock;			/* port lock */

>  	unsigned long		iobase;			/* in/out[bwl] */

> @@ -255,6 +278,7 @@ struct uart_port {

>  	struct gpio_desc	*rs485_term_gpio;	/* enable RS485 bus termination */

>  	struct serial_iso7816   iso7816;

>  	void			*private_data;		/* generic platform data pointer */

> +	struct uart_nmi_state	nmi_state;


Should there be an #ifdef CONFIG_CONSOLE_POLL on the nmi_state.

It's a bit odd... you've created a static inline (uart_nmi_active) to
get rid of ifdef'ry in the .c file and then only provide a single
implementation anyway.


>  };

>  

>  static inline int serial_port_in(struct uart_port *up, int offset)

> @@ -475,4 +499,23 @@ extern int uart_handle_break(struct uart_port *port);

>  					 !((cflag) & CLOCAL))

>  

>  int uart_get_rs485_mode(struct uart_port *port);

> +

> +/*

> + * The following are helper functions for the NMI aware serial drivers.

> + */

> +

> +int uart_nmi_state_init(struct uart_port *port);

> +void uart_nmi_handle_rx_data(struct uart_port *port,

> +			     struct uart_nmi_rx_data *rx_data);

> +

> +static inline bool uart_nmi_active(struct uart_port *port)

> +{

> +	return port->nmi_state.active;

> +}

> +

> +static inline void uart_set_nmi_active(struct uart_port *port, bool val)

> +{

> +	port->nmi_state.active = val;

> +}


What is the point of this helper?


Daniel.
Sumit Garg July 14, 2020, 10:19 a.m. UTC | #2
On Mon, 13 Jul 2020 at 19:48, Daniel Thompson
<daniel.thompson@linaro.org> wrote:
>

> On Fri, Jul 10, 2020 at 06:42:03PM +0530, Sumit Garg wrote:

> > With the advent of pseudo NMIs on arm64 platforms, its been now possible

> > to have NMI driven serial drivers which enables us to have magic sysrq

> > running in NMI context that could be helpful to debug hardlockup

> > scenarios especially via enabling kernel debugger to run in NMI context.

> >

> > So add corresponding NMI helper APIs that can be leveraged by serial

> > drivers. Also, make sysrq handler APIs NMI safe.

> >

> > Signed-off-by: Sumit Garg <sumit.garg@linaro.org>

> > ---

> >  drivers/tty/serial/serial_core.c | 97 +++++++++++++++++++++++++++++++++++++++-

> >  include/linux/serial_core.h      | 43 ++++++++++++++++++

> >  2 files changed, 138 insertions(+), 2 deletions(-)

> >

> > diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c

> > index 57840cf..a59d7ff 100644

> > --- a/drivers/tty/serial/serial_core.c

> > +++ b/drivers/tty/serial/serial_core.c

> > @@ -3181,16 +3181,28 @@ static bool uart_try_toggle_sysrq(struct uart_port *port, unsigned int ch)

> >               return true;

> >       }

> >

> > -     schedule_work(&sysrq_enable_work);

> > +     if (in_nmi())

> > +             irq_work_queue(&port->nmi_state.sysrq_toggle_work);

> > +     else

> > +             schedule_work(&sysrq_enable_work);

> >

> >       port->sysrq = 0;

> >       return true;

> >  }

> > +

> > +static void uart_nmi_toggle_work(struct irq_work *work)

> > +{

> > +     schedule_work(&sysrq_enable_work);

> > +}

> >  #else

> >  static inline bool uart_try_toggle_sysrq(struct uart_port *port, unsigned int ch)

> >  {

> >       return false;

> >  }

> > +

> > +static void uart_nmi_toggle_work(struct irq_work *work)

> > +{

> > +}

> >  #endif

> >

> >  int uart_handle_sysrq_char(struct uart_port *port, unsigned int ch)

> > @@ -3273,12 +3285,93 @@ int uart_handle_break(struct uart_port *port)

> >               port->sysrq = 0;

> >       }

> >

> > -     if (port->flags & UPF_SAK)

> > +     if (in_nmi() && (port->flags & UPF_SAK))

> > +             irq_work_queue(&port->nmi_state.sysrq_sak_work);

> > +     else if (port->flags & UPF_SAK)

> >               do_SAK(state->port.tty);

> >       return 0;

> >  }

> >  EXPORT_SYMBOL_GPL(uart_handle_break);

> >

> > +static void uart_nmi_sak_work(struct irq_work *work)

> > +{

> > +     struct uart_nmi_state *nmi_state =

> > +         container_of(work, struct uart_nmi_state, sysrq_sak_work);

> > +     struct uart_port *port = nmi_state->port;

> > +

> > +     do_SAK(port->state->port.tty);

> > +}

> > +

> > +static void uart_nmi_rx_work(struct irq_work *rx_work)

> > +{

> > +     struct uart_nmi_state *nmi_state =

> > +         container_of(rx_work, struct uart_nmi_state, rx_work);

> > +     struct uart_port *port = nmi_state->port;

> > +     struct uart_nmi_rx_data rx_data;

> > +

> > +     /*

> > +      * In polling mode, serial device is initialized much prior to

> > +      * TTY port becoming active. This scenario is especially useful

> > +      * from debugging perspective such that magic sysrq or debugger

> > +      * entry would still be possible even when TTY port isn't

> > +      * active (consider a boot hang case or if a user hasn't opened

> > +      * the serial port). So we discard any other RX data apart from

> > +      * magic sysrq commands in case TTY port isn't active.

> > +      */

> > +     if (!port->state || !tty_port_active(&port->state->port)) {

> > +             kfifo_reset(&nmi_state->rx_fifo);

> > +             return;

> > +     }

> > +

> > +     if (unlikely(!kfifo_len(&nmi_state->rx_fifo)))

> > +             return;

>

> If this is unlikely then why bother check it at all?

>


Okay, will remove this check.

>

> > +

> > +     spin_lock(&port->lock);

> > +     while (kfifo_out(&nmi_state->rx_fifo, &rx_data, 1))

> > +             uart_insert_char(port, rx_data.ch, rx_data.overrun,

> > +                              rx_data.ch, rx_data.flag);

> > +     spin_unlock(&port->lock);

> > +

> > +     tty_flip_buffer_push(&port->state->port);

> > +}

> > +

> > +static void uart_nmi_tx_work(struct irq_work *tx_work)

> > +{

> > +     struct uart_nmi_state *nmi_state =

> > +         container_of(tx_work, struct uart_nmi_state, tx_work);

> > +     struct uart_port *port = nmi_state->port;

> > +

> > +     spin_lock(&port->lock);

> > +     if (nmi_state->tx_irq_callback)

> > +             nmi_state->tx_irq_callback(port);

> > +     spin_unlock(&port->lock);

> > +}

> > +

> > +void uart_nmi_handle_rx_data(struct uart_port *port,

> > +                          struct uart_nmi_rx_data *rx_data)

> > +{

> > +     WARN_ON_ONCE(!kfifo_in(&port->nmi_state.rx_fifo, rx_data, 1));

>

> This isn't error handling. This is just yelling if an error happens

> (which might be OK except that the upper level includes a mechanism to

> report overruns when we get too much data on the serial line).

>


I guess you are referring to following mechanism to report overruns:

--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -3350,7 +3350,8 @@ static void uart_nmi_tx_work(struct irq_work *tx_work)
 void uart_nmi_handle_rx_data(struct uart_port *port,
                             struct uart_nmi_rx_data *rx_data)
 {
-       WARN_ON_ONCE(!kfifo_in(&port->nmi_state.rx_fifo, rx_data, 1));
+       if (!kfifo_in(&port->nmi_state.rx_fifo, rx_data, 1))
+               ++port->icount.buf_overrun;
 }
 EXPORT_SYMBOL_GPL(uart_nmi_handle_rx_data);

If yes, then I will include the above change in this patch.

>

> > +}

> > +EXPORT_SYMBOL_GPL(uart_nmi_handle_rx_data);

> > +

> > +int uart_nmi_state_init(struct uart_port *port)

> > +{

> > +     int ret;

> > +

> > +     ret = kfifo_alloc(&port->nmi_state.rx_fifo, 256, GFP_KERNEL);

> > +     if (ret)

> > +             return ret;

> > +

> > +     port->nmi_state.port = port;

> > +     init_irq_work(&port->nmi_state.tx_work, uart_nmi_tx_work);

> > +     init_irq_work(&port->nmi_state.rx_work, uart_nmi_rx_work);

> > +     init_irq_work(&port->nmi_state.sysrq_sak_work, uart_nmi_sak_work);

> > +     init_irq_work(&port->nmi_state.sysrq_toggle_work, uart_nmi_toggle_work);

> > +

> > +     return ret;

> > +}

> > +EXPORT_SYMBOL_GPL(uart_nmi_state_init);

> > +

> >  EXPORT_SYMBOL(uart_write_wakeup);

> >  EXPORT_SYMBOL(uart_register_driver);

> >  EXPORT_SYMBOL(uart_unregister_driver);

> > diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h

> > index 9fd550e..51d62f4 100644

> > --- a/include/linux/serial_core.h

> > +++ b/include/linux/serial_core.h

> > @@ -18,6 +18,8 @@

> >  #include <linux/tty.h>

> >  #include <linux/mutex.h>

> >  #include <linux/sysrq.h>

> > +#include <linux/irq_work.h>

> > +#include <linux/kfifo.h>

> >  #include <uapi/linux/serial_core.h>

> >

> >  #ifdef CONFIG_SERIAL_CORE_CONSOLE

> > @@ -103,6 +105,27 @@ struct uart_icount {

> >  typedef unsigned int __bitwise upf_t;

> >  typedef unsigned int __bitwise upstat_t;

> >

> > +struct uart_nmi_rx_data {

> > +     unsigned int            ch;

> > +     unsigned int            overrun;

> > +     unsigned int            flag;

> > +};

> > +

> > +struct uart_nmi_state {

> > +     bool                    active;

> > +

> > +     struct irq_work         tx_work;

> > +     void                    (*tx_irq_callback)(struct uart_port *port);

> > +

> > +     struct irq_work         rx_work;

> > +     DECLARE_KFIFO_PTR(rx_fifo, struct uart_nmi_rx_data);

> > +

> > +     struct irq_work         sysrq_sak_work;

> > +     struct irq_work         sysrq_toggle_work;

> > +

> > +     struct uart_port        *port;

>

> This structure is statically allocated... can we use containerof instead

> of backlinks?

>


Earlier I was using container_of() only but only to make NMI APIs to
look a little cleaner to avoid multiple use of container_of(), I added
this backlink.

But if backlinks aren't preferred for statically allocated structures
then will switch back to use of container_of().

>

> > +};

> > +

> >  struct uart_port {

> >       spinlock_t              lock;                   /* port lock */

> >       unsigned long           iobase;                 /* in/out[bwl] */

> > @@ -255,6 +278,7 @@ struct uart_port {

> >       struct gpio_desc        *rs485_term_gpio;       /* enable RS485 bus termination */

> >       struct serial_iso7816   iso7816;

> >       void                    *private_data;          /* generic platform data pointer */

> > +     struct uart_nmi_state   nmi_state;

>

> Should there be an #ifdef CONFIG_CONSOLE_POLL on the nmi_state.

>


Yeah we could keep it under CONFIG_CONSOLE_POLL since currently we
only enable NMI state under poll_init().

But it seems like this NMI feature can be enabled independently of
polling mode as well. AFAIK, CONFIG_CONSOLE_POLL is there to provide
safer uart poll read/write APIs for the debugger which should be able
to work irrespective of serial device interrupt being requested as an
NMI or an IRQ.

So that was the initial idea to not keep it under CONFIG_CONSOLE_POLL.
But if you still prefer it otherwise then I can do corresponding
changes.

> It's a bit odd... you've created a static inline (uart_nmi_active) to

> get rid of ifdef'ry in the .c file and then only provide a single

> implementation anyway.

>


Yeah initial idea was to get rid of ifdef'ry but still I saw them
being useful to provide a consistent set of wrapper to manage NMI
state.

>

> >  };

> >

> >  static inline int serial_port_in(struct uart_port *up, int offset)

> > @@ -475,4 +499,23 @@ extern int uart_handle_break(struct uart_port *port);

> >                                        !((cflag) & CLOCAL))

> >

> >  int uart_get_rs485_mode(struct uart_port *port);

> > +

> > +/*

> > + * The following are helper functions for the NMI aware serial drivers.

> > + */

> > +

> > +int uart_nmi_state_init(struct uart_port *port);

> > +void uart_nmi_handle_rx_data(struct uart_port *port,

> > +                          struct uart_nmi_rx_data *rx_data);

> > +

> > +static inline bool uart_nmi_active(struct uart_port *port)

> > +{

> > +     return port->nmi_state.active;

> > +}

> > +

> > +static inline void uart_set_nmi_active(struct uart_port *port, bool val)

> > +{

> > +     port->nmi_state.active = val;

> > +}

>

> What is the point of this helper?

>


These are just to provide a consistent set of wrappers to
alter/retrieve NMI state. Also, I got this motivation for them after
looking at how tty port flags were managed.

-Sumit

>

> Daniel.
diff mbox series

Patch

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 57840cf..a59d7ff 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -3181,16 +3181,28 @@  static bool uart_try_toggle_sysrq(struct uart_port *port, unsigned int ch)
 		return true;
 	}
 
-	schedule_work(&sysrq_enable_work);
+	if (in_nmi())
+		irq_work_queue(&port->nmi_state.sysrq_toggle_work);
+	else
+		schedule_work(&sysrq_enable_work);
 
 	port->sysrq = 0;
 	return true;
 }
+
+static void uart_nmi_toggle_work(struct irq_work *work)
+{
+	schedule_work(&sysrq_enable_work);
+}
 #else
 static inline bool uart_try_toggle_sysrq(struct uart_port *port, unsigned int ch)
 {
 	return false;
 }
+
+static void uart_nmi_toggle_work(struct irq_work *work)
+{
+}
 #endif
 
 int uart_handle_sysrq_char(struct uart_port *port, unsigned int ch)
@@ -3273,12 +3285,93 @@  int uart_handle_break(struct uart_port *port)
 		port->sysrq = 0;
 	}
 
-	if (port->flags & UPF_SAK)
+	if (in_nmi() && (port->flags & UPF_SAK))
+		irq_work_queue(&port->nmi_state.sysrq_sak_work);
+	else if (port->flags & UPF_SAK)
 		do_SAK(state->port.tty);
 	return 0;
 }
 EXPORT_SYMBOL_GPL(uart_handle_break);
 
+static void uart_nmi_sak_work(struct irq_work *work)
+{
+	struct uart_nmi_state *nmi_state =
+	    container_of(work, struct uart_nmi_state, sysrq_sak_work);
+	struct uart_port *port = nmi_state->port;
+
+	do_SAK(port->state->port.tty);
+}
+
+static void uart_nmi_rx_work(struct irq_work *rx_work)
+{
+	struct uart_nmi_state *nmi_state =
+	    container_of(rx_work, struct uart_nmi_state, rx_work);
+	struct uart_port *port = nmi_state->port;
+	struct uart_nmi_rx_data rx_data;
+
+	/*
+	 * In polling mode, serial device is initialized much prior to
+	 * TTY port becoming active. This scenario is especially useful
+	 * from debugging perspective such that magic sysrq or debugger
+	 * entry would still be possible even when TTY port isn't
+	 * active (consider a boot hang case or if a user hasn't opened
+	 * the serial port). So we discard any other RX data apart from
+	 * magic sysrq commands in case TTY port isn't active.
+	 */
+	if (!port->state || !tty_port_active(&port->state->port)) {
+		kfifo_reset(&nmi_state->rx_fifo);
+		return;
+	}
+
+	if (unlikely(!kfifo_len(&nmi_state->rx_fifo)))
+		return;
+
+	spin_lock(&port->lock);
+	while (kfifo_out(&nmi_state->rx_fifo, &rx_data, 1))
+		uart_insert_char(port, rx_data.ch, rx_data.overrun,
+				 rx_data.ch, rx_data.flag);
+	spin_unlock(&port->lock);
+
+	tty_flip_buffer_push(&port->state->port);
+}
+
+static void uart_nmi_tx_work(struct irq_work *tx_work)
+{
+	struct uart_nmi_state *nmi_state =
+	    container_of(tx_work, struct uart_nmi_state, tx_work);
+	struct uart_port *port = nmi_state->port;
+
+	spin_lock(&port->lock);
+	if (nmi_state->tx_irq_callback)
+		nmi_state->tx_irq_callback(port);
+	spin_unlock(&port->lock);
+}
+
+void uart_nmi_handle_rx_data(struct uart_port *port,
+			     struct uart_nmi_rx_data *rx_data)
+{
+	WARN_ON_ONCE(!kfifo_in(&port->nmi_state.rx_fifo, rx_data, 1));
+}
+EXPORT_SYMBOL_GPL(uart_nmi_handle_rx_data);
+
+int uart_nmi_state_init(struct uart_port *port)
+{
+	int ret;
+
+	ret = kfifo_alloc(&port->nmi_state.rx_fifo, 256, GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	port->nmi_state.port = port;
+	init_irq_work(&port->nmi_state.tx_work, uart_nmi_tx_work);
+	init_irq_work(&port->nmi_state.rx_work, uart_nmi_rx_work);
+	init_irq_work(&port->nmi_state.sysrq_sak_work, uart_nmi_sak_work);
+	init_irq_work(&port->nmi_state.sysrq_toggle_work, uart_nmi_toggle_work);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(uart_nmi_state_init);
+
 EXPORT_SYMBOL(uart_write_wakeup);
 EXPORT_SYMBOL(uart_register_driver);
 EXPORT_SYMBOL(uart_unregister_driver);
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 9fd550e..51d62f4 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -18,6 +18,8 @@ 
 #include <linux/tty.h>
 #include <linux/mutex.h>
 #include <linux/sysrq.h>
+#include <linux/irq_work.h>
+#include <linux/kfifo.h>
 #include <uapi/linux/serial_core.h>
 
 #ifdef CONFIG_SERIAL_CORE_CONSOLE
@@ -103,6 +105,27 @@  struct uart_icount {
 typedef unsigned int __bitwise upf_t;
 typedef unsigned int __bitwise upstat_t;
 
+struct uart_nmi_rx_data {
+	unsigned int		ch;
+	unsigned int		overrun;
+	unsigned int		flag;
+};
+
+struct uart_nmi_state {
+	bool			active;
+
+	struct irq_work		tx_work;
+	void			(*tx_irq_callback)(struct uart_port *port);
+
+	struct irq_work		rx_work;
+	DECLARE_KFIFO_PTR(rx_fifo, struct uart_nmi_rx_data);
+
+	struct irq_work		sysrq_sak_work;
+	struct irq_work		sysrq_toggle_work;
+
+	struct uart_port	*port;
+};
+
 struct uart_port {
 	spinlock_t		lock;			/* port lock */
 	unsigned long		iobase;			/* in/out[bwl] */
@@ -255,6 +278,7 @@  struct uart_port {
 	struct gpio_desc	*rs485_term_gpio;	/* enable RS485 bus termination */
 	struct serial_iso7816   iso7816;
 	void			*private_data;		/* generic platform data pointer */
+	struct uart_nmi_state	nmi_state;
 };
 
 static inline int serial_port_in(struct uart_port *up, int offset)
@@ -475,4 +499,23 @@  extern int uart_handle_break(struct uart_port *port);
 					 !((cflag) & CLOCAL))
 
 int uart_get_rs485_mode(struct uart_port *port);
+
+/*
+ * The following are helper functions for the NMI aware serial drivers.
+ */
+
+int uart_nmi_state_init(struct uart_port *port);
+void uart_nmi_handle_rx_data(struct uart_port *port,
+			     struct uart_nmi_rx_data *rx_data);
+
+static inline bool uart_nmi_active(struct uart_port *port)
+{
+	return port->nmi_state.active;
+}
+
+static inline void uart_set_nmi_active(struct uart_port *port, bool val)
+{
+	port->nmi_state.active = val;
+}
+
 #endif /* LINUX_SERIAL_CORE_H */