MN10300: fix SMP synchronization between txdma and serial driver
authorMark Salter <msalter@redhat.com>
Wed, 12 Dec 2012 15:36:38 +0000 (15:36 +0000)
committerDavid Howells <dhowells@redhat.com>
Wed, 12 Dec 2012 15:46:14 +0000 (15:46 +0000)
The SoC serial port driver uses a high priority interrupt to handle tx of
characters in the tx ring buffer. The driver needs to disable/enable this IRQ
from outside of irq context. The original code to do this is not foolproof on
SMP machines because the IRQ running on one core could still access the serial
port for a short time after the driver running on another core disables the
interrupt. This patch adds a flag to tell the IRQ handler that the driver
wants to disable the interrupt. After seeing the flag, the IRQ handler will
immediately disable the interrupt and exit. After setting the flag, the driver
will wait for interrupt to be disabled by the IRQ handler.

Signed-off-by: Mark Salter <msalter@redhat.com>
Signed-off-by: David Howells <dhowells@redhat.com>
arch/mn10300/kernel/asm-offsets.c
arch/mn10300/kernel/mn10300-serial-low.S
arch/mn10300/kernel/mn10300-serial.c
arch/mn10300/kernel/mn10300-serial.h

index 96f24fab7de6ee0e559369e195a2fa7b5120a8f2..47b3bb0c04ffc4f9db8395a6042235a5c7fab105 100644 (file)
@@ -96,7 +96,7 @@ void foo(void)
        OFFSET(__rx_outp,               mn10300_serial_port, rx_outp);
        OFFSET(__uart_state,            mn10300_serial_port, uart.state);
        OFFSET(__tx_xchar,              mn10300_serial_port, tx_xchar);
-       OFFSET(__tx_break,              mn10300_serial_port, tx_break);
+       OFFSET(__tx_flags,              mn10300_serial_port, tx_flags);
        OFFSET(__intr_flags,            mn10300_serial_port, intr_flags);
        OFFSET(__rx_icr,                mn10300_serial_port, rx_icr);
        OFFSET(__tx_icr,                mn10300_serial_port, tx_icr);
index dfc1b6f2fa9a4b70f251d8766c773c39fc7733fe..b95e76caf4fae24e0841500cde288bcd775dbc5f 100644 (file)
@@ -118,8 +118,8 @@ ENTRY(mn10300_serial_vdma_tx_handler)
        movbu   d2,(e3)                 # ACK the interrupt
        movhu   (e3),d2                 # flush
 
-       btst    0x01,(__tx_break,a3)    # handle transmit break request
-       bne     mnsc_vdma_tx_break
+       btst    0xFF,(__tx_flags,a3)    # handle transmit flags
+       bne     mnsc_vdma_tx_flags
 
        movbu   (SCxSTR,e2),d2          # don't try and transmit a char if the
                                        # buffer is not empty
@@ -171,10 +171,13 @@ mnsc_vdma_tx_empty:
        bset    MNSCx_TX_EMPTY,(__intr_flags,a3)
        bra     mnsc_vdma_tx_done
 
-mnsc_vdma_tx_break:
+mnsc_vdma_tx_flags:
+       btst    MNSCx_TX_STOP,(__tx_flags,a3)
+       bne     mnsc_vdma_tx_stop
        movhu   (SCxCTR,e2),d2          # turn on break mode
        or      SC01CTR_BKE,d2
        movhu   d2,(SCxCTR,e2)
+mnsc_vdma_tx_stop:
        mov     +(NUM2GxICR_LEVEL(CONFIG_MN10300_SERIAL_IRQ_LEVEL)|GxICR_DETECT),d2
        movhu   d2,(e3)                 # disable transmit interrupts on this
                                        # channel
index cac0f0da92037e3cd9fe61d9e5b063d0b23d2670..587545cb7e4c82ed3884928f148fed445a784259 100644 (file)
@@ -444,25 +444,53 @@ struct mn10300_serial_int mn10300_serial_int_tbl[NR_IRQS];
 
 static void mn10300_serial_dis_tx_intr(struct mn10300_serial_port *port)
 {
-       unsigned long flags;
+       int retries = 100;
        u16 x;
 
-       flags = arch_local_cli_save();
-       *port->tx_icr = NUM2GxICR_LEVEL(CONFIG_MN10300_SERIAL_IRQ_LEVEL);
-       x = *port->tx_icr;
-       arch_local_irq_restore(flags);
+       /* nothing to do if irq isn't set up */
+       if (!mn10300_serial_int_tbl[port->tx_irq].port)
+               return;
+
+       port->tx_flags |= MNSCx_TX_STOP;
+       mb();
+
+       /*
+        * Here we wait for the irq to be disabled. Either it already is
+        * disabled or we wait some number of retries for the VDMA handler
+        * to disable it. The retries give the VDMA handler enough time to
+        * run to completion if it was already in progress. If the VDMA IRQ
+        * is enabled but the handler is not yet running when arrive here,
+        * the STOP flag will prevent the handler from conflicting with the
+        * driver code following this loop.
+        */
+       while ((*port->tx_icr & GxICR_ENABLE) && retries-- > 0)
+               ;
+       if (retries <= 0) {
+               *port->tx_icr =
+                       NUM2GxICR_LEVEL(CONFIG_MN10300_SERIAL_IRQ_LEVEL);
+               x = *port->tx_icr;
+       }
 }
 
 static void mn10300_serial_en_tx_intr(struct mn10300_serial_port *port)
 {
-       unsigned long flags;
        u16 x;
 
-       flags = arch_local_cli_save();
+       /* nothing to do if irq isn't set up */
+       if (!mn10300_serial_int_tbl[port->tx_irq].port)
+               return;
+
+       /* stop vdma irq if not already stopped */
+       if (!(port->tx_flags & MNSCx_TX_STOP))
+               mn10300_serial_dis_tx_intr(port);
+
+       port->tx_flags &= ~MNSCx_TX_STOP;
+       mb();
+
        *port->tx_icr =
-               NUM2GxICR_LEVEL(CONFIG_MN10300_SERIAL_IRQ_LEVEL) | GxICR_ENABLE;
+               NUM2GxICR_LEVEL(CONFIG_MN10300_SERIAL_IRQ_LEVEL) |
+               GxICR_ENABLE | GxICR_REQUEST | GxICR_DETECT;
        x = *port->tx_icr;
-       arch_local_irq_restore(flags);
 }
 
 static void mn10300_serial_dis_rx_intr(struct mn10300_serial_port *port)
@@ -807,8 +835,6 @@ static void mn10300_serial_start_tx(struct uart_port *_port)
        struct mn10300_serial_port *port =
                container_of(_port, struct mn10300_serial_port, uart);
 
-       u16 x;
-
        _enter("%s{%lu}",
               port->name,
               CIRC_CNT(&port->uart.state->xmit.head,
@@ -816,14 +842,7 @@ static void mn10300_serial_start_tx(struct uart_port *_port)
                        UART_XMIT_SIZE));
 
        /* kick the virtual DMA controller */
-       arch_local_cli();
-       x = *port->tx_icr;
-       x |= GxICR_ENABLE;
-
-       if (*port->_status & SC01STR_TBF)
-               x &= ~(GxICR_REQUEST | GxICR_DETECT);
-       else
-               x |= GxICR_REQUEST | GxICR_DETECT;
+       mn10300_serial_en_tx_intr(port);
 
        _debug("CTR=%04hx ICR=%02hx STR=%04x TMD=%02hx TBR=%04hx ICR=%04hx",
               *port->_control, *port->_intr, *port->_status,
@@ -831,10 +850,6 @@ static void mn10300_serial_start_tx(struct uart_port *_port)
               (port->div_timer == MNSCx_DIV_TIMER_8BIT) ?
                   *(volatile u8 *)port->_tmxbr : *port->_tmxbr,
               *port->tx_icr);
-
-       *port->tx_icr = x;
-       x = *port->tx_icr;
-       arch_local_sti();
 }
 
 /*
@@ -844,13 +859,17 @@ static void mn10300_serial_send_xchar(struct uart_port *_port, char ch)
 {
        struct mn10300_serial_port *port =
                container_of(_port, struct mn10300_serial_port, uart);
+       unsigned long flags;
 
        _enter("%s,%02x", port->name, ch);
 
        if (likely(port->gdbstub)) {
                port->tx_xchar = ch;
-               if (ch)
+               if (ch) {
+                       spin_lock_irqsave(&port->uart.lock, flags);
                        mn10300_serial_en_tx_intr(port);
+                       spin_unlock_irqrestore(&port->uart.lock, flags);
+               }
        }
 }
 
@@ -911,18 +930,21 @@ static void mn10300_serial_break_ctl(struct uart_port *_port, int ctl)
 {
        struct mn10300_serial_port *port =
                container_of(_port, struct mn10300_serial_port, uart);
+       unsigned long flags;
 
        _enter("%s,%d", port->name, ctl);
 
+       spin_lock_irqsave(&port->uart.lock, flags);
        if (ctl) {
                /* tell the virtual DMA handler to assert BREAK */
-               port->tx_break = 1;
+               port->tx_flags |= MNSCx_TX_BREAK;
                mn10300_serial_en_tx_intr(port);
        } else {
-               port->tx_break = 0;
+               port->tx_flags &= ~MNSCx_TX_BREAK;
                *port->_control &= ~SC01CTR_BKE;
                mn10300_serial_en_tx_intr(port);
        }
+       spin_unlock_irqrestore(&port->uart.lock, flags);
 }
 
 /*
@@ -945,6 +967,7 @@ static int mn10300_serial_startup(struct uart_port *_port)
                return -ENOMEM;
 
        port->rx_inp = port->rx_outp = 0;
+       port->tx_flags = 0;
 
        /* finally, enable the device */
        *port->_intr = SC01ICR_TI;
@@ -994,14 +1017,22 @@ error:
  */
 static void mn10300_serial_shutdown(struct uart_port *_port)
 {
+       unsigned long flags;
        u16 x;
        struct mn10300_serial_port *port =
                container_of(_port, struct mn10300_serial_port, uart);
 
        _enter("%s", port->name);
 
+       spin_lock_irqsave(&_port->lock, flags);
+       mn10300_serial_dis_tx_intr(port);
+
+       *port->rx_icr = NUM2GxICR_LEVEL(CONFIG_MN10300_SERIAL_IRQ_LEVEL);
+       x = *port->rx_icr;
+       port->tx_flags = 0;
+       spin_unlock_irqrestore(&_port->lock, flags);
+
        /* disable the serial port and its baud rate timer */
-       port->tx_break = 0;
        *port->_control &= ~(SC01CTR_TXE | SC01CTR_RXE | SC01CTR_BKE);
        *port->_tmxmd = 0;
 
@@ -1016,12 +1047,8 @@ static void mn10300_serial_shutdown(struct uart_port *_port)
        free_irq(port->rx_irq, port);
        free_irq(port->tx_irq, port);
 
-       arch_local_cli();
-       *port->rx_icr = NUM2GxICR_LEVEL(CONFIG_MN10300_SERIAL_IRQ_LEVEL);
-       x = *port->rx_icr;
-       *port->tx_icr = NUM2GxICR_LEVEL(CONFIG_MN10300_SERIAL_IRQ_LEVEL);
-       x = *port->tx_icr;
-       arch_local_sti();
+       mn10300_serial_int_tbl[port->tx_irq].port = NULL;
+       mn10300_serial_int_tbl[port->rx_irq].port = NULL;
 }
 
 /*
@@ -1549,17 +1576,24 @@ static void mn10300_serial_console_write(struct console *co,
 {
        struct mn10300_serial_port *port;
        unsigned i;
-       u16 scxctr, txicr, tmp;
+       u16 scxctr;
        u8 tmxmd;
+       unsigned long flags;
+       int locked = 1;
 
        port = mn10300_serial_ports[co->index];
 
+       local_irq_save(flags);
+       if (port->uart.sysrq) {
+               /* mn10300_serial_interrupt() already took the lock */
+               locked = 0;
+       } else if (oops_in_progress) {
+               locked = spin_trylock(&port->uart.lock);
+       } else
+               spin_lock(&port->uart.lock);
+
        /* firstly hijack the serial port from the "virtual DMA" controller */
-       arch_local_cli();
-       txicr = *port->tx_icr;
-       *port->tx_icr = NUM2GxICR_LEVEL(CONFIG_MN10300_SERIAL_IRQ_LEVEL);
-       tmp = *port->tx_icr;
-       arch_local_sti();
+       mn10300_serial_dis_tx_intr(port);
 
        /* the transmitter may be disabled */
        scxctr = *port->_control;
@@ -1613,10 +1647,11 @@ static void mn10300_serial_console_write(struct console *co,
        if (!(scxctr & SC01CTR_TXE))
                *port->_control = scxctr;
 
-       arch_local_cli();
-       *port->tx_icr = txicr;
-       tmp = *port->tx_icr;
-       arch_local_sti();
+       mn10300_serial_en_tx_intr(port);
+
+       if (locked)
+               spin_unlock(&port->uart.lock);
+       local_irq_restore(flags);
 }
 
 /*
index 6796499bf789b0136a79a066afaa44a0b901f072..0004e61619a5831740e9b413433cfe7b8eee4099 100644 (file)
 #define MNSCx_TX_SPACE         0x04
 #define MNSCx_TX_EMPTY         0x08
 
+/* tx_flags bits */
+#define MNSCx_TX_BREAK         0x01
+#define MNSCx_TX_STOP          0x02
+
 #ifndef __ASSEMBLY__
 
 struct mn10300_serial_port {
@@ -36,7 +40,7 @@ struct mn10300_serial_port {
        unsigned                rx_inp;         /* pointer to rx input offset */
        unsigned                rx_outp;        /* pointer to rx output offset */
        u8                      tx_xchar;       /* high-priority XON/XOFF buffer */
-       u8                      tx_break;       /* transmit break request */
+       u8                      tx_flags;       /* transmit break/stop request */
        u8                      intr_flags;     /* interrupt flags */
        volatile u16            *rx_icr;        /* Rx interrupt control register */
        volatile u16            *tx_icr;        /* Tx interrupt control register */