Commit a1fee899 authored by Ryan Case's avatar Ryan Case Committed by Greg Kroah-Hartman

tty: serial: qcom_geni_serial: Fix softlock

Transfers were being divided into device FIFO sized (64 byte max)
operations which would poll for completion within a spin_lock_irqsave /
spin_unlock_irqrestore block. This both made things slow by waiting for
the FIFO to completely drain before adding further data and would also
result in softlocks on large transmissions.

This patch allows larger transfers with continuous FIFO additions as
space becomes available and removes polling from the interrupt handler.
Signed-off-by: default avatarRyan Case <ryandcase@chromium.org>
Reviewed-by: default avatarStephen Boyd <swboyd@chromium.org>
Reviewed-by: default avatarDouglas Anderson <dianders@chromium.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent d02337d2
...@@ -117,6 +117,8 @@ struct qcom_geni_serial_port { ...@@ -117,6 +117,8 @@ struct qcom_geni_serial_port {
u32 *rx_fifo; u32 *rx_fifo;
u32 loopback; u32 loopback;
bool brk; bool brk;
unsigned int tx_remaining;
}; };
static const struct uart_ops qcom_geni_console_pops; static const struct uart_ops qcom_geni_console_pops;
...@@ -437,6 +439,7 @@ static void qcom_geni_serial_console_write(struct console *co, const char *s, ...@@ -437,6 +439,7 @@ static void qcom_geni_serial_console_write(struct console *co, const char *s,
struct qcom_geni_serial_port *port; struct qcom_geni_serial_port *port;
bool locked = true; bool locked = true;
unsigned long flags; unsigned long flags;
u32 geni_status;
WARN_ON(co->index < 0 || co->index >= GENI_UART_CONS_PORTS); WARN_ON(co->index < 0 || co->index >= GENI_UART_CONS_PORTS);
...@@ -450,6 +453,8 @@ static void qcom_geni_serial_console_write(struct console *co, const char *s, ...@@ -450,6 +453,8 @@ static void qcom_geni_serial_console_write(struct console *co, const char *s,
else else
spin_lock_irqsave(&uport->lock, flags); spin_lock_irqsave(&uport->lock, flags);
geni_status = readl_relaxed(uport->membase + SE_GENI_STATUS);
/* Cancel the current write to log the fault */ /* Cancel the current write to log the fault */
if (!locked) { if (!locked) {
geni_se_cancel_m_cmd(&port->se); geni_se_cancel_m_cmd(&port->se);
...@@ -463,9 +468,19 @@ static void qcom_geni_serial_console_write(struct console *co, const char *s, ...@@ -463,9 +468,19 @@ static void qcom_geni_serial_console_write(struct console *co, const char *s,
} }
writel_relaxed(M_CMD_CANCEL_EN, uport->membase + writel_relaxed(M_CMD_CANCEL_EN, uport->membase +
SE_GENI_M_IRQ_CLEAR); SE_GENI_M_IRQ_CLEAR);
} else if ((geni_status & M_GENI_CMD_ACTIVE) && !port->tx_remaining) {
/*
* It seems we can't interrupt existing transfers if all data
* has been sent, in which case we need to look for done first.
*/
qcom_geni_serial_poll_tx_done(uport);
} }
__qcom_geni_serial_console_write(uport, s, count); __qcom_geni_serial_console_write(uport, s, count);
if (port->tx_remaining)
qcom_geni_serial_setup_tx(uport, port->tx_remaining);
if (locked) if (locked)
spin_unlock_irqrestore(&uport->lock, flags); spin_unlock_irqrestore(&uport->lock, flags);
} }
...@@ -697,40 +712,45 @@ static void qcom_geni_serial_handle_rx(struct uart_port *uport, bool drop) ...@@ -697,40 +712,45 @@ static void qcom_geni_serial_handle_rx(struct uart_port *uport, bool drop)
port->handle_rx(uport, total_bytes, drop); port->handle_rx(uport, total_bytes, drop);
} }
static void qcom_geni_serial_handle_tx(struct uart_port *uport) static void qcom_geni_serial_handle_tx(struct uart_port *uport, bool done,
bool active)
{ {
struct qcom_geni_serial_port *port = to_dev_port(uport, uport); struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
struct circ_buf *xmit = &uport->state->xmit; struct circ_buf *xmit = &uport->state->xmit;
size_t avail; size_t avail;
size_t remaining; size_t remaining;
size_t pending;
int i; int i;
u32 status; u32 status;
unsigned int chunk; unsigned int chunk;
int tail; int tail;
u32 irq_en;
chunk = uart_circ_chars_pending(xmit);
status = readl_relaxed(uport->membase + SE_GENI_TX_FIFO_STATUS); status = readl_relaxed(uport->membase + SE_GENI_TX_FIFO_STATUS);
/* Both FIFO and framework buffer are drained */
if (!chunk && !status) { /* Complete the current tx command before taking newly added data */
if (active)
pending = port->tx_remaining;
else
pending = uart_circ_chars_pending(xmit);
/* All data has been transmitted and acknowledged as received */
if (!pending && !status && done) {
qcom_geni_serial_stop_tx(uport); qcom_geni_serial_stop_tx(uport);
goto out_write_wakeup; goto out_write_wakeup;
} }
if (!uart_console(uport)) { avail = port->tx_fifo_depth - (status & TX_FIFO_WC);
irq_en = readl_relaxed(uport->membase + SE_GENI_M_IRQ_EN); avail *= port->tx_bytes_pw;
irq_en &= ~(M_TX_FIFO_WATERMARK_EN);
writel_relaxed(0, uport->membase + SE_GENI_TX_WATERMARK_REG);
writel_relaxed(irq_en, uport->membase + SE_GENI_M_IRQ_EN);
}
avail = (port->tx_fifo_depth - port->tx_wm) * port->tx_bytes_pw;
tail = xmit->tail; tail = xmit->tail;
chunk = min3((size_t)chunk, (size_t)(UART_XMIT_SIZE - tail), avail); chunk = min3(avail, pending, (size_t)(UART_XMIT_SIZE - tail));
if (!chunk) if (!chunk)
goto out_write_wakeup; goto out_write_wakeup;
qcom_geni_serial_setup_tx(uport, chunk); if (!port->tx_remaining) {
qcom_geni_serial_setup_tx(uport, pending);
port->tx_remaining = pending;
}
remaining = chunk; remaining = chunk;
for (i = 0; i < chunk; ) { for (i = 0; i < chunk; ) {
...@@ -749,11 +769,10 @@ static void qcom_geni_serial_handle_tx(struct uart_port *uport) ...@@ -749,11 +769,10 @@ static void qcom_geni_serial_handle_tx(struct uart_port *uport)
tail += tx_bytes; tail += tx_bytes;
uport->icount.tx += tx_bytes; uport->icount.tx += tx_bytes;
remaining -= tx_bytes; remaining -= tx_bytes;
port->tx_remaining -= tx_bytes;
} }
xmit->tail = tail & (UART_XMIT_SIZE - 1); xmit->tail = tail & (UART_XMIT_SIZE - 1);
if (uart_console(uport))
qcom_geni_serial_poll_tx_done(uport);
out_write_wakeup: out_write_wakeup:
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(uport); uart_write_wakeup(uport);
...@@ -763,6 +782,7 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) ...@@ -763,6 +782,7 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev)
{ {
unsigned int m_irq_status; unsigned int m_irq_status;
unsigned int s_irq_status; unsigned int s_irq_status;
unsigned int geni_status;
struct uart_port *uport = dev; struct uart_port *uport = dev;
unsigned long flags; unsigned long flags;
unsigned int m_irq_en; unsigned int m_irq_en;
...@@ -776,6 +796,7 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) ...@@ -776,6 +796,7 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev)
spin_lock_irqsave(&uport->lock, flags); spin_lock_irqsave(&uport->lock, flags);
m_irq_status = readl_relaxed(uport->membase + SE_GENI_M_IRQ_STATUS); m_irq_status = readl_relaxed(uport->membase + SE_GENI_M_IRQ_STATUS);
s_irq_status = readl_relaxed(uport->membase + SE_GENI_S_IRQ_STATUS); s_irq_status = readl_relaxed(uport->membase + SE_GENI_S_IRQ_STATUS);
geni_status = readl_relaxed(uport->membase + SE_GENI_STATUS);
m_irq_en = readl_relaxed(uport->membase + SE_GENI_M_IRQ_EN); m_irq_en = readl_relaxed(uport->membase + SE_GENI_M_IRQ_EN);
writel_relaxed(m_irq_status, uport->membase + SE_GENI_M_IRQ_CLEAR); writel_relaxed(m_irq_status, uport->membase + SE_GENI_M_IRQ_CLEAR);
writel_relaxed(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR); writel_relaxed(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR);
...@@ -790,7 +811,8 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) ...@@ -790,7 +811,8 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev)
if (m_irq_status & (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN) && if (m_irq_status & (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN) &&
m_irq_en & (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN)) m_irq_en & (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN))
qcom_geni_serial_handle_tx(uport); qcom_geni_serial_handle_tx(uport, m_irq_status & M_CMD_DONE_EN,
geni_status & M_GENI_CMD_ACTIVE);
if (s_irq_status & S_GP_IRQ_0_EN || s_irq_status & S_GP_IRQ_1_EN) { if (s_irq_status & S_GP_IRQ_0_EN || s_irq_status & S_GP_IRQ_1_EN) {
if (s_irq_status & S_GP_IRQ_0_EN) if (s_irq_status & S_GP_IRQ_0_EN)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment