serial: sh-sci: Fix NULL pointer dereference if HIGHMEM is enabled
[firefly-linux-kernel-4.4.55.git] / drivers / tty / serial / sh-sci.c
index 049036dfb6ddf32c25775e0f41603ed92b873c2c..70e16f402e3108f48d4ab7129c2832eab4ea2efe 100644 (file)
@@ -104,14 +104,13 @@ struct sci_port {
        struct dma_chan                 *chan_rx;
 
 #ifdef CONFIG_SERIAL_SH_SCI_DMA
-       struct dma_async_tx_descriptor  *desc_tx;
-       struct dma_async_tx_descriptor  *desc_rx[2];
        dma_cookie_t                    cookie_tx;
        dma_cookie_t                    cookie_rx[2];
        dma_cookie_t                    active_rx;
        dma_addr_t                      tx_dma_addr;
        unsigned int                    tx_dma_len;
        struct scatterlist              sg_rx[2];
+       void                            *rx_buf[2];
        size_t                          buf_len_rx;
        struct sh_dmae_slave            param_tx;
        struct sh_dmae_slave            param_rx;
@@ -1285,9 +1284,6 @@ static void sci_dma_tx_complete(void *arg)
 
        port->icount.tx += s->tx_dma_len;
 
-       async_tx_ack(s->desc_tx);
-       s->desc_tx = NULL;
-
        if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
                uart_write_wakeup(port);
 
@@ -1306,37 +1302,35 @@ static void sci_dma_tx_complete(void *arg)
 }
 
 /* Locking: called with port lock held */
-static int sci_dma_rx_push(struct sci_port *s, size_t count)
+static int sci_dma_rx_push(struct sci_port *s, void *buf, size_t count)
 {
        struct uart_port *port = &s->port;
        struct tty_port *tport = &port->state->port;
-       int i, active, room;
-
-       room = tty_buffer_request_room(tport, count);
+       int copied;
 
-       if (s->active_rx == s->cookie_rx[0]) {
-               active = 0;
-       } else if (s->active_rx == s->cookie_rx[1]) {
-               active = 1;
-       } else {
-               dev_err(port->dev, "%s: Rx cookie %d not found!\n", __func__,
-                       s->active_rx);
-               return 0;
+       copied = tty_insert_flip_string(tport, buf, count);
+       if (copied < count) {
+               dev_warn(port->dev, "Rx overrun: dropping %zu bytes\n",
+                        count - copied);
+               port->icount.buf_overrun++;
        }
 
-       if (room < count)
-               dev_warn(port->dev, "Rx overrun: dropping %zu bytes\n",
-                        count - room);
-       if (!room)
-               return room;
+       port->icount.rx += copied;
+
+       return copied;
+}
 
-       for (i = 0; i < room; i++)
-               tty_insert_flip_char(tport, ((u8 *)sg_virt(&s->sg_rx[active]))[i],
-                                    TTY_NORMAL);
+static int sci_dma_rx_find_active(struct sci_port *s)
+{
+       unsigned int i;
 
-       port->icount.rx += room;
+       for (i = 0; i < ARRAY_SIZE(s->cookie_rx); i++)
+               if (s->active_rx == s->cookie_rx[i])
+                       return i;
 
-       return room;
+       dev_err(s->port.dev, "%s: Rx cookie %d not found!\n", __func__,
+               s->active_rx);
+       return -1;
 }
 
 static void sci_dma_rx_complete(void *arg)
@@ -1344,14 +1338,16 @@ static void sci_dma_rx_complete(void *arg)
        struct sci_port *s = arg;
        struct uart_port *port = &s->port;
        unsigned long flags;
-       int count;
+       int active, count = 0;
 
        dev_dbg(port->dev, "%s(%d) active cookie %d\n", __func__, port->line,
                s->active_rx);
 
        spin_lock_irqsave(&port->lock, flags);
 
-       count = sci_dma_rx_push(s, s->buf_len_rx);
+       active = sci_dma_rx_find_active(s);
+       if (active >= 0)
+               count = sci_dma_rx_push(s, s->rx_buf[active], s->buf_len_rx);
 
        mod_timer(&s->rx_timer, jiffies + s->rx_timeout);
 
@@ -1367,11 +1363,15 @@ static void sci_rx_dma_release(struct sci_port *s, bool enable_pio)
 {
        struct dma_chan *chan = s->chan_rx;
        struct uart_port *port = &s->port;
+       unsigned long flags;
 
+       spin_lock_irqsave(&port->lock, flags);
        s->chan_rx = NULL;
        s->cookie_rx[0] = s->cookie_rx[1] = -EINVAL;
-       dma_free_coherent(chan->device->dev, s->buf_len_rx * 2,
-                         sg_virt(&s->sg_rx[0]), sg_dma_address(&s->sg_rx[0]));
+       spin_unlock_irqrestore(&port->lock, flags);
+       dmaengine_terminate_all(chan);
+       dma_free_coherent(chan->device->dev, s->buf_len_rx * 2, s->rx_buf[0],
+                         sg_dma_address(&s->sg_rx[0]));
        dma_release_channel(chan);
        if (enable_pio)
                sci_start_rx(port);
@@ -1381,9 +1381,13 @@ static void sci_tx_dma_release(struct sci_port *s, bool enable_pio)
 {
        struct dma_chan *chan = s->chan_tx;
        struct uart_port *port = &s->port;
+       unsigned long flags;
 
+       spin_lock_irqsave(&port->lock, flags);
        s->chan_tx = NULL;
        s->cookie_tx = -EINVAL;
+       spin_unlock_irqrestore(&port->lock, flags);
+       dmaengine_terminate_all(chan);
        dma_unmap_single(chan->device->dev, s->tx_dma_addr, UART_XMIT_SIZE,
                         DMA_TO_DEVICE);
        dma_release_channel(chan);
@@ -1401,29 +1405,17 @@ static void sci_submit_rx(struct sci_port *s)
                struct dma_async_tx_descriptor *desc;
 
                desc = dmaengine_prep_slave_sg(chan,
-                       sg, 1, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
+                       sg, 1, DMA_DEV_TO_MEM,
+                       DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+               if (!desc)
+                       goto fail;
 
-               if (desc) {
-                       s->desc_rx[i] = desc;
-                       desc->callback = sci_dma_rx_complete;
-                       desc->callback_param = s;
-                       s->cookie_rx[i] = dmaengine_submit(desc);
-               }
+               desc->callback = sci_dma_rx_complete;
+               desc->callback_param = s;
+               s->cookie_rx[i] = dmaengine_submit(desc);
+               if (dma_submit_error(s->cookie_rx[i]))
+                       goto fail;
 
-               if (!desc || dma_submit_error(s->cookie_rx[i])) {
-                       if (i) {
-                               async_tx_ack(s->desc_rx[0]);
-                               s->cookie_rx[0] = -EINVAL;
-                       }
-                       if (desc) {
-                               async_tx_ack(desc);
-                               s->cookie_rx[i] = -EINVAL;
-                       }
-                       dev_warn(s->port.dev,
-                                "Failed to re-start Rx DMA, using PIO\n");
-                       sci_rx_dma_release(s, true);
-                       return;
-               }
                dev_dbg(s->port.dev, "%s(): cookie %d to #%d\n", __func__,
                        s->cookie_rx[i], i);
        }
@@ -1431,6 +1423,16 @@ static void sci_submit_rx(struct sci_port *s)
        s->active_rx = s->cookie_rx[0];
 
        dma_async_issue_pending(chan);
+       return;
+
+fail:
+       if (i)
+               dmaengine_terminate_all(chan);
+       for (i = 0; i < 2; i++)
+               s->cookie_rx[i] = -EINVAL;
+       s->active_rx = -EINVAL;
+       dev_warn(s->port.dev, "Failed to re-start Rx DMA, using PIO\n");
+       sci_rx_dma_release(s, true);
 }
 
 static void work_fn_rx(struct work_struct *work)
@@ -1440,24 +1442,20 @@ static void work_fn_rx(struct work_struct *work)
        struct dma_async_tx_descriptor *desc;
        struct dma_tx_state state;
        enum dma_status status;
+       unsigned long flags;
        int new;
 
-       if (s->active_rx == s->cookie_rx[0]) {
-               new = 0;
-       } else if (s->active_rx == s->cookie_rx[1]) {
-               new = 1;
-       } else {
-               dev_err(port->dev, "%s: Rx cookie %d not found!\n", __func__,
-                       s->active_rx);
+       spin_lock_irqsave(&port->lock, flags);
+       new = sci_dma_rx_find_active(s);
+       if (new < 0) {
+               spin_unlock_irqrestore(&port->lock, flags);
                return;
        }
-       desc = s->desc_rx[new];
 
        status = dmaengine_tx_status(s->chan_rx, s->active_rx, &state);
        if (status != DMA_COMPLETE) {
                /* Handle incomplete DMA receive */
                struct dma_chan *chan = s->chan_rx;
-               unsigned long flags;
                unsigned int read;
                int count;
 
@@ -1466,29 +1464,40 @@ static void work_fn_rx(struct work_struct *work)
                dev_dbg(port->dev, "Read %u bytes with cookie %d\n", read,
                        s->active_rx);
 
-               spin_lock_irqsave(&port->lock, flags);
-               count = sci_dma_rx_push(s, read);
-               spin_unlock_irqrestore(&port->lock, flags);
+               count = sci_dma_rx_push(s, s->rx_buf[new], read);
 
                if (count)
                        tty_flip_buffer_push(&port->state->port);
 
-               sci_submit_rx(s);
+               spin_unlock_irqrestore(&port->lock, flags);
 
+               sci_submit_rx(s);
                return;
        }
 
+       desc = dmaengine_prep_slave_sg(s->chan_rx, &s->sg_rx[new], 1,
+                                      DMA_DEV_TO_MEM,
+                                      DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+       if (!desc)
+               goto fail;
+
+       desc->callback = sci_dma_rx_complete;
+       desc->callback_param = s;
        s->cookie_rx[new] = dmaengine_submit(desc);
-       if (dma_submit_error(s->cookie_rx[new])) {
-               dev_warn(port->dev, "Failed submitting Rx DMA descriptor\n");
-               sci_rx_dma_release(s, true);
-               return;
-       }
+       if (dma_submit_error(s->cookie_rx[new]))
+               goto fail;
 
        s->active_rx = s->cookie_rx[!new];
 
        dev_dbg(port->dev, "%s: cookie %d #%d, new active cookie %d\n",
                __func__, s->cookie_rx[new], new, s->active_rx);
+       spin_unlock_irqrestore(&port->lock, flags);
+       return;
+
+fail:
+       spin_unlock_irqrestore(&port->lock, flags);
+       dev_warn(port->dev, "Failed submitting Rx DMA descriptor\n");
+       sci_rx_dma_release(s, true);
 }
 
 static void work_fn_tx(struct work_struct *work)
@@ -1528,7 +1537,6 @@ static void work_fn_tx(struct work_struct *work)
                                   DMA_TO_DEVICE);
 
        spin_lock_irq(&port->lock);
-       s->desc_tx = desc;
        desc->callback = sci_dma_tx_complete;
        desc->callback_param = s;
        spin_unlock_irq(&port->lock);
@@ -1726,18 +1734,16 @@ static void sci_request_dma(struct uart_port *port)
        chan = dma_request_channel(mask, filter, param);
        dev_dbg(port->dev, "%s: RX: got channel %p\n", __func__, chan);
        if (chan) {
-               dma_addr_t dma[2];
-               void *buf[2];
-               int i;
+               unsigned int i;
+               dma_addr_t dma;
+               void *buf;
 
                s->chan_rx = chan;
 
                s->buf_len_rx = 2 * max_t(size_t, 16, port->fifosize);
-               buf[0] = dma_alloc_coherent(chan->device->dev,
-                                           s->buf_len_rx * 2, &dma[0],
-                                           GFP_KERNEL);
-
-               if (!buf[0]) {
+               buf = dma_alloc_coherent(chan->device->dev, s->buf_len_rx * 2,
+                                        &dma, GFP_KERNEL);
+               if (!buf) {
                        dev_warn(port->dev,
                                 "Failed to allocate Rx dma buffer, using PIO\n");
                        dma_release_channel(chan);
@@ -1746,16 +1752,16 @@ static void sci_request_dma(struct uart_port *port)
                        return;
                }
 
-               buf[1] = buf[0] + s->buf_len_rx;
-               dma[1] = dma[0] + s->buf_len_rx;
-
                for (i = 0; i < 2; i++) {
                        struct scatterlist *sg = &s->sg_rx[i];
 
                        sg_init_table(sg, 1);
-                       sg_set_page(sg, virt_to_page(buf[i]), s->buf_len_rx,
-                                   (uintptr_t)buf[i] & ~PAGE_MASK);
-                       sg_dma_address(sg) = dma[i];
+                       s->rx_buf[i] = buf;
+                       sg_dma_address(sg) = dma;
+                       sg->length = s->buf_len_rx;
+
+                       buf += s->buf_len_rx;
+                       dma += s->buf_len_rx;
                }
 
                INIT_WORK(&s->work_rx, work_fn_rx);