Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 352a065f authored by Ryan Case's avatar Ryan Case Committed by Chandana Kishori Chiluveru
Browse files

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.

Change-Id: Icba9b7f7cc3fd8312e7f2473b3a5665ff8aedec2
Signed-off-by: default avatarRyan Case <ryandcase@chromium.org>
Git-commit: a1fee899e5bed457afc20a6a2ff3915a95cc5942
Git-repo: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git


[akashast@codeaurora.org: Applied patch to downstream file - msm_geni_serial.c]
Signed-off-by: default avatarAlok Chauhan <alokc@codeaurora.org>
Signed-off-by: default avatarAkash Asthana <akashast@codeaurora.org>
Signed-off-by: default avatarChandana Kishori Chiluveru <cchiluve@codeaurora.org>
parent 7edbd7e0
Loading
Loading
Loading
Loading
+47 −16
Original line number Original line Diff line number Diff line
@@ -176,6 +176,7 @@ struct msm_geni_serial_port {
	int edge_count;
	int edge_count;
	bool manual_flow;
	bool manual_flow;
	struct msm_geni_serial_ver_info ver_info;
	struct msm_geni_serial_ver_info ver_info;
	u32 cur_tx_remaining;
};
};


static const struct uart_ops msm_geni_serial_pops;
static const struct uart_ops msm_geni_serial_pops;
@@ -781,6 +782,7 @@ static void msm_geni_serial_console_write(struct console *co, const char *s,
	struct msm_geni_serial_port *port;
	struct msm_geni_serial_port *port;
	bool locked = true;
	bool locked = true;
	unsigned long flags;
	unsigned long flags;
	unsigned int geni_status;


	WARN_ON(co->index < 0 || co->index >= GENI_UART_NR_PORTS);
	WARN_ON(co->index < 0 || co->index >= GENI_UART_NR_PORTS);


@@ -794,6 +796,8 @@ static void msm_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_cancel_m_cmd(uport->membase);
		geni_cancel_m_cmd(uport->membase);
@@ -807,9 +811,17 @@ static void msm_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->cur_tx_remaining)
		/* It seems we can interrupt existing transfers unless all data
		 * has been sent, in which case we need to look for done first.
		 */
		msm_geni_serial_poll_cancel_tx(uport);


	__msm_geni_serial_console_write(uport, s, count);
	__msm_geni_serial_console_write(uport, s, count);

	if (port->cur_tx_remaining)
		msm_geni_serial_setup_tx(uport, port->cur_tx_remaining);

	if (locked)
	if (locked)
		spin_unlock_irqrestore(&uport->lock, flags);
		spin_unlock_irqrestore(&uport->lock, flags);
}
}
@@ -1275,13 +1287,15 @@ static int msm_geni_serial_handle_rx(struct uart_port *uport, bool drop_rx)
	return ret;
	return ret;
}
}


static int msm_geni_serial_handle_tx(struct uart_port *uport)
static int msm_geni_serial_handle_tx(struct uart_port *uport, bool done,
		bool active)
{
{
	int ret = 0;
	int ret = 0;
	struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
	struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
	struct circ_buf *xmit = &uport->state->xmit;
	struct circ_buf *xmit = &uport->state->xmit;
	unsigned int avail_fifo_bytes = 0;
	unsigned int avail_fifo_bytes = 0;
	unsigned int bytes_remaining = 0;
	unsigned int bytes_remaining = 0;
	unsigned int pending;
	int i = 0;
	int i = 0;
	unsigned int tx_fifo_status;
	unsigned int tx_fifo_status;
	unsigned int xmit_size;
	unsigned int xmit_size;
@@ -1289,28 +1303,40 @@ static int msm_geni_serial_handle_tx(struct uart_port *uport)
		(uart_console(uport) ? 1 : (msm_port->tx_fifo_width >> 3));
		(uart_console(uport) ? 1 : (msm_port->tx_fifo_width >> 3));
	int temp_tail = 0;
	int temp_tail = 0;


	xmit_size = uart_circ_chars_pending(xmit);
	tx_fifo_status = geni_read_reg_nolog(uport->membase,
	tx_fifo_status = geni_read_reg_nolog(uport->membase,
					SE_GENI_TX_FIFO_STATUS);
					SE_GENI_TX_FIFO_STATUS);
	/* Both FIFO and framework buffer are drained */

	if (!xmit_size && !tx_fifo_status) {
	/* Complete the current tx command before taking newly added data */
	if (active)
		pending = msm_port->cur_tx_remaining;
	else
		pending = uart_circ_chars_pending(xmit);

	/* All data has been transmitted and acknowledged as received */
	if (!pending && !tx_fifo_status && done) {
		msm_geni_serial_stop_tx(uport);
		msm_geni_serial_stop_tx(uport);
		goto exit_handle_tx;
		goto exit_handle_tx;
	}
	}


	avail_fifo_bytes = (msm_port->tx_fifo_depth - msm_port->tx_wm) *
	avail_fifo_bytes = msm_port->tx_fifo_depth - (tx_fifo_status &
							fifo_width_bytes;
								TX_FIFO_WC);
	temp_tail = xmit->tail & (UART_XMIT_SIZE - 1);
	avail_fifo_bytes *= fifo_width_bytes;
	if (avail_fifo_bytes < 0)
		avail_fifo_bytes = 0;


	if (xmit_size > (UART_XMIT_SIZE - temp_tail))
	temp_tail = xmit->tail;
		xmit_size = (UART_XMIT_SIZE - temp_tail);
	xmit_size = min3((unsigned int)pending,
	if (xmit_size > avail_fifo_bytes)
		(unsigned int)(UART_XMIT_SIZE - temp_tail), avail_fifo_bytes);
		xmit_size = avail_fifo_bytes;
	if (!xmit_size)
	if (!xmit_size)
		goto exit_handle_tx;
		goto exit_handle_tx;


	msm_geni_serial_setup_tx(uport, xmit_size);
	if (!msm_port->cur_tx_remaining) {
		msm_geni_serial_setup_tx(uport, pending);
		msm_port->cur_tx_remaining = pending;
	}

	bytes_remaining = xmit_size;
	bytes_remaining = xmit_size;

	while (i < xmit_size) {
	while (i < xmit_size) {
		unsigned int tx_bytes;
		unsigned int tx_bytes;
		unsigned int buf = 0;
		unsigned int buf = 0;
@@ -1321,17 +1347,18 @@ static int msm_geni_serial_handle_tx(struct uart_port *uport)


		for (c = 0; c < tx_bytes ; c++)
		for (c = 0; c < tx_bytes ; c++)
			buf |= (xmit->buf[temp_tail + c] << (c * 8));
			buf |= (xmit->buf[temp_tail + c] << (c * 8));

		geni_write_reg_nolog(buf, uport->membase, SE_GENI_TX_FIFOn);
		geni_write_reg_nolog(buf, uport->membase, SE_GENI_TX_FIFOn);

		i += tx_bytes;
		i += tx_bytes;
		bytes_remaining -= tx_bytes;
		bytes_remaining -= tx_bytes;
		uport->icount.tx += tx_bytes;
		uport->icount.tx += tx_bytes;
		temp_tail += tx_bytes;
		temp_tail += tx_bytes;
		msm_port->cur_tx_remaining -= tx_bytes;
		/* Ensure FIFO write goes through */
		/* Ensure FIFO write goes through */
		wmb();
		wmb();
	}
	}
	xmit->tail = temp_tail & (UART_XMIT_SIZE - 1);
	xmit->tail = temp_tail & (UART_XMIT_SIZE - 1);
	if (uart_console(uport))
		msm_geni_serial_poll_cancel_tx(uport);
exit_handle_tx:
exit_handle_tx:
	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
		uart_write_wakeup(uport);
		uart_write_wakeup(uport);
@@ -1428,6 +1455,7 @@ static irqreturn_t msm_geni_serial_isr(int isr, void *dev)
	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;
	unsigned int geni_status;
	struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
	struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
	struct tty_port *tport = &uport->state->port;
	struct tty_port *tport = &uport->state->port;
	bool drop_rx = false;
	bool drop_rx = false;
@@ -1449,6 +1477,7 @@ static irqreturn_t msm_geni_serial_isr(int isr, void *dev)
	dma = geni_read_reg_nolog(uport->membase, SE_GENI_DMA_MODE_EN);
	dma = geni_read_reg_nolog(uport->membase, SE_GENI_DMA_MODE_EN);
	dma_tx_status = geni_read_reg_nolog(uport->membase, SE_DMA_TX_IRQ_STAT);
	dma_tx_status = geni_read_reg_nolog(uport->membase, SE_DMA_TX_IRQ_STAT);
	dma_rx_status = geni_read_reg_nolog(uport->membase, SE_DMA_RX_IRQ_STAT);
	dma_rx_status = geni_read_reg_nolog(uport->membase, SE_DMA_RX_IRQ_STAT);
	geni_status = readl_relaxed(uport->membase + SE_GENI_STATUS);


	geni_write_reg_nolog(m_irq_status, uport->membase, SE_GENI_M_IRQ_CLEAR);
	geni_write_reg_nolog(m_irq_status, uport->membase, SE_GENI_M_IRQ_CLEAR);
	geni_write_reg_nolog(s_irq_status, uport->membase, SE_GENI_S_IRQ_CLEAR);
	geni_write_reg_nolog(s_irq_status, uport->membase, SE_GENI_S_IRQ_CLEAR);
@@ -1469,7 +1498,9 @@ static irqreturn_t msm_geni_serial_isr(int isr, void *dev)
	if (!dma) {
	if (!dma) {
		if ((m_irq_status & m_irq_en) &
		if ((m_irq_status & m_irq_en) &
		    (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN))
		    (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN))
			msm_geni_serial_handle_tx(uport);
			msm_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) ||
		if ((s_irq_status & S_GP_IRQ_0_EN) ||
			(s_irq_status & S_GP_IRQ_1_EN)) {
			(s_irq_status & S_GP_IRQ_1_EN)) {