diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
index 974a47f24084..1e739aceff39 100644
--- a/drivers/tty/serial/8250/8250.h
+++ b/drivers/tty/serial/8250/8250.h
@@ -11,6 +11,7 @@
#include <linux/serial_8250.h>
#include <linux/serial_core.h>
#include <linux/dmaengine.h>
+#include <linux/workqueue.h>
#include "../serial_mctrl_gpio.h"
@@ -19,6 +20,10 @@ struct uart_8250_dma {
int (*rx_dma)(struct uart_8250_port *p);
void (*prepare_tx_dma)(struct uart_8250_port *p);
void (*prepare_rx_dma)(struct uart_8250_port *p);
+ /* Completion processing with the port lock held. */
+ void (*tx_complete)(struct uart_8250_port *p);
+ struct work_struct tx_flush_work;
+ struct uart_8250_port *port;
/* Filter function */
dma_filter_fn fn;@@ -52,7 +57,9 @@ struct uart_8250_dma {
unsigned char tx_err;
unsigned char rx_running;
/* Protected by the UART port lock. */
- bool shutting_down;
+ bool shutting_down:1;
+ bool tx_terminating:1;
+ bool tx_flushed:1;
};
struct old_serial_port {@@ -400,8 +407,15 @@ static inline int is_omap1510_8250(struct uart_8250_port *pt)
#endif
#ifdef CONFIG_SERIAL_8250_DMA
+static inline bool serial8250_tx_dma_blocked(struct uart_8250_dma *dma)
+{
+ return dma->shutting_down || dma->tx_terminating;
+}
+
extern int serial8250_tx_dma(struct uart_8250_port *);
extern void serial8250_tx_dma_flush(struct uart_8250_port *);
+void serial8250_tx_dma_sync_buffer(struct uart_8250_port *p,
+ struct scatterlist *sgl, unsigned int nents);
extern int serial8250_rx_dma(struct uart_8250_port *);
extern void serial8250_rx_dma_flush(struct uart_8250_port *);
extern int serial8250_request_dma(struct uart_8250_port *);@@ -434,7 +448,7 @@ static inline void serial8250_tx_dma_pause(struct uart_8250_port *p)
{
struct uart_8250_dma *dma = p->dma;
- if (!dma->tx_running)
+ if (serial8250_tx_dma_blocked(dma) || !dma->tx_running)
return;
dmaengine_pause(dma->txchan);@@ -444,7 +458,7 @@ static inline void serial8250_tx_dma_resume(struct uart_8250_port *p)
{
struct uart_8250_dma *dma = p->dma;
- if (!dma->tx_running)
+ if (serial8250_tx_dma_blocked(dma) || !dma->tx_running)
return;
dmaengine_resume(dma->txchan);diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c
index 43c55cb991a5..739a8238e4de 100644
--- a/drivers/tty/serial/8250/8250_dma.c
+++ b/drivers/tty/serial/8250/8250_dma.c
@@ -11,34 +11,79 @@
#include "8250.h"
-static void __dma_tx_complete(void *param)
+static void serial8250_tx_dma_complete(struct uart_8250_port *p)
{
- struct uart_8250_port *p = param;
struct uart_8250_dma *dma = p->dma;
- unsigned long flags;
int ret;
- uart_port_lock_irqsave(&p->port, &flags);
- if (dma->shutting_down)
- goto out;
-
dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
UART_XMIT_SIZE, DMA_TO_DEVICE);
dma->tx_running = 0;
- uart_xmit_advance(&p->port, dma->tx_size);
+ if (dma->tx_flushed) {
+ p->port.icount.tx += dma->tx_size;
+ uart_xmit_unblock(&p->port);
+ } else {
+ uart_xmit_advance(&p->port, dma->tx_size);
+ }
uart_write_wakeup_if_needed(&p->port);
ret = serial8250_tx_dma(p);
if (ret || !dma->tx_running)
serial8250_set_THRI(p);
+}
-out:
- uart_port_unlock_irqrestore(&p->port, flags);
+static void __dma_tx_complete(void *param)
+{
+ struct uart_8250_port *p = param;
+ struct uart_8250_dma *dma = p->dma;
+
+ guard(uart_port_lock_irqsave)(&p->port);
+
+ if (serial8250_tx_dma_blocked(dma) || !dma->tx_running)
+ return;
+ serial8250_tx_dma_complete(p);
+}
+
+static void serial8250_tx_dma_flush_work(struct work_struct *work)
+{
+ struct uart_8250_dma *dma = container_of(work, struct uart_8250_dma, tx_flush_work);
+ struct uart_8250_port *p = dma->port;
+
+ /* Neither the channel nor its buffer may be reused before this returns. */
+ dmaengine_synchronize(dma->txchan);
+
+ guard(serial8250_rpm)(p);
+
+ guard(uart_port_lock_irqsave)(&p->port);
+
+ dma->tx_terminating = false;
+ dma->tx_size = 0;
+ if (dma->shutting_down)
+ return;
+ dma->tx_complete(p);
}
+void serial8250_tx_dma_sync_buffer(struct uart_8250_port *p,
+ struct scatterlist *sgl, unsigned int nents)
+{
+ struct uart_8250_dma *dma = p->dma;
+ struct device *dev = dmaengine_get_dma_device(dma->txchan);
+ struct scatterlist *sg;
+ unsigned int i;
+
+ /* DMA reads the FIFO directly; its occupied ranges remain reserved. */
+ for_each_sg(sgl, sg, nents, i) {
+ size_t offset = sg_dma_address(sg) - dma->tx_addr;
+
+ dma_sync_single_range_for_device(dev, dma->tx_addr, offset,
+ sg_dma_len(sg), DMA_TO_DEVICE);
+ }
+}
+EXPORT_SYMBOL_GPL(serial8250_tx_dma_sync_buffer);
+
static void __dma_rx_complete(struct uart_8250_port *p)
{
struct uart_8250_dma *dma = p->dma;@@ -99,7 +144,7 @@ int serial8250_tx_dma(struct uart_8250_port *p)
int i;
int ret;
- if (dma->shutting_down)
+ if (serial8250_tx_dma_blocked(dma))
return 0;
if (dma->tx_running) {@@ -129,24 +174,25 @@ int serial8250_tx_dma(struct uart_8250_port *p)
dma->tx_size += sg_dma_len(sg);
serial8250_do_prepare_tx_dma(p);
+ serial8250_tx_dma_sync_buffer(p, sgl, ret);
desc = dmaengine_prep_slave_sg(dma->txchan, sgl, ret,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!desc) {
+ dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
+ UART_XMIT_SIZE, DMA_TO_DEVICE);
ret = -EBUSY;
goto err;
}
dma->tx_running = 1;
+ dma->tx_flushed = false;
+ dma->tx_complete = serial8250_tx_dma_complete;
desc->callback = __dma_tx_complete;
desc->callback_param = p;
dma->tx_cookie = dmaengine_submit(desc);
-
- dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
- UART_XMIT_SIZE, DMA_TO_DEVICE);
-
dma_async_issue_pending(dma->txchan);
serial8250_clear_THRI(p);
dma->tx_err = 0;@@ -160,32 +206,31 @@ int serial8250_tx_dma(struct uart_8250_port *p)
void serial8250_tx_dma_flush(struct uart_8250_port *p)
{
struct uart_8250_dma *dma = p->dma;
+ int ret;
- if (!dma->tx_running)
+ if (serial8250_tx_dma_blocked(dma) || !dma->tx_running)
return;
/*
* kfifo_reset() has been called by the serial core, avoid
- * advancing and underflowing in __dma_tx_complete().
+ * advancing the reset FIFO when the old transfer completes.
*/
- dma->tx_size = 0;
+ dma->tx_flushed = true;
+ /* The core reset the FIFO indices, but DMA may still read its storage. */
+ uart_xmit_block(&p->port);
/*
* We can't use `dmaengine_terminate_sync` because `uart_flush_buffer` is
* holding the uart port spinlock.
*/
- dmaengine_terminate_async(dma->txchan);
-
- /*
- * The callback might or might not run. If it doesn't run, we need to ensure
- * that `tx_running` is cleared so that we can schedule new transactions.
- * If it does run, then the zombie callback will clear `tx_running` again
- * and perform a no-op since `tx_size` was cleared above.
- *
- * In either case, we ASSUME the DMA transaction will terminate before we
- * issue a new `serial8250_tx_dma`.
- */
- dma->tx_running = 0;
+ dma->tx_terminating = true;
+ ret = dmaengine_terminate_async(dma->txchan);
+ if (ret) {
+ /* Keep the buffer owned by the transfer until its callback runs. */
+ dma->tx_terminating = false;
+ return;
+ }
+ schedule_work(&dma->tx_flush_work);
}
int serial8250_rx_dma(struct uart_8250_port *p)@@ -310,21 +355,28 @@ int serial8250_request_dma(struct uart_8250_port *p)
goto err;
}
- /* TX buffer */
+ /* Normal TX uses serial core's FIFO; flush reserves it until retirement. */
dma->tx_addr = dma_map_single(tx_dev, p->port.state->port.xmit_buf,
UART_XMIT_SIZE, DMA_TO_DEVICE);
if (dma_mapping_error(tx_dev, dma->tx_addr)) {
- dma_free_coherent(rx_dev, dma->rx_size,
- dma->rx_buf, dma->rx_addr);
ret = -ENOMEM;
- goto err;
+ goto free_rx;
}
+ dma_sync_single_for_cpu(tx_dev, dma->tx_addr, UART_XMIT_SIZE, DMA_TO_DEVICE);
+
+ dma->port = p;
+ INIT_WORK(&dma->tx_flush_work, serial8250_tx_dma_flush_work);
- scoped_guard(uart_port_lock_irqsave, &p->port)
+ scoped_guard(uart_port_lock_irqsave, &p->port) {
+ dma->tx_terminating = false;
+ dma->tx_flushed = false;
dma->shutting_down = false;
+ }
dev_dbg_ratelimited(p->port.dev, "got both dma channels\n");
return 0;
+free_rx:
+ dma_free_coherent(rx_dev, dma->rx_size, dma->rx_buf, dma->rx_addr);
err:
dma_release_channel(dma->txchan);
release_rx:@@ -340,6 +392,8 @@ void serial8250_release_dma(struct uart_8250_port *p)
if (!dma)
return;
+ cancel_work_sync(&dma->tx_flush_work);
+
/* Release RX resources */
dmaengine_terminate_sync(dma->rxchan);
dma->rx_running = 0;
@@ -354,7 +408,11 @@ void serial8250_release_dma(struct uart_8250_port *p)
UART_XMIT_SIZE, DMA_TO_DEVICE);
dma_release_channel(dma->txchan);
dma->txchan = NULL;
- dma->tx_running = 0;
+ scoped_guard(uart_port_lock_irqsave, &p->port) {
+ dma->tx_running = 0;
+ dma->tx_terminating = false;
+ uart_xmit_unblock(&p->port);
+ }
dev_dbg_ratelimited(p->port.dev, "dma channels released\n");
}diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c
index 477d645be9b0..a7cd5b918622 100644
--- a/drivers/tty/serial/8250/8250_omap.c
+++ b/drivers/tty/serial/8250/8250_omap.c
@@ -1099,7 +1099,12 @@ static void omap_8250_tx_dma_complete(struct uart_8250_port *p)
dma->tx_running = 0;
- uart_xmit_advance(&p->port, dma->tx_size);
+ if (dma->tx_flushed) {
+ p->port.icount.tx += dma->tx_size;
+ uart_xmit_unblock(&p->port);
+ } else {
+ uart_xmit_advance(&p->port, dma->tx_size);
+ }
if (priv->delayed_restore) {
priv->delayed_restore = 0;@@ -1131,7 +1136,7 @@ static void omap_8250_dma_tx_complete(void *param)
guard(uart_port_lock_irqsave)(&p->port);
- if (dma->shutting_down)
+ if (serial8250_tx_dma_blocked(dma) || !dma->tx_running)
return;
omap_8250_tx_dma_complete(p);
}
@@ -1146,7 +1151,7 @@ static int omap_8250_tx_dma(struct uart_8250_port *p)
int skip_byte = -1;
int ret;
- if (dma->shutting_down || dma->tx_running)
+ if (serial8250_tx_dma_blocked(dma) || dma->tx_running)
return 0;
if (uart_tx_stopped(&p->port) || kfifo_is_empty(&tport->xmit_fifo)) {
@@ -1205,24 +1210,26 @@ static int omap_8250_tx_dma(struct uart_8250_port *p)
goto err;
}
+ serial8250_tx_dma_sync_buffer(p, &sg, 1);
+
desc = dmaengine_prep_slave_sg(dma->txchan, &sg, 1, DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!desc) {
+ dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
+ UART_XMIT_SIZE, DMA_TO_DEVICE);
ret = -EBUSY;
goto err;
}
dma->tx_size = sg_dma_len(&sg);
dma->tx_running = 1;
+ dma->tx_flushed = false;
+ dma->tx_complete = omap_8250_tx_dma_complete;
desc->callback = omap_8250_dma_tx_complete;
desc->callback_param = p;
dma->tx_cookie = dmaengine_submit(desc);
-
- dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
- UART_XMIT_SIZE, DMA_TO_DEVICE);
-
dma_async_issue_pending(dma->txchan);
if (dma->tx_err)
dma->tx_err = 0;
--
2.53.0