2019-09-19 14:43:19 +00:00
|
|
|
From 3e3c13488e4efa0236c47a98ee5e759bf1f7c757 Mon Sep 17 00:00:00 2001
|
2019-07-31 16:23:26 +00:00
|
|
|
From: Phil Elwell <phil@raspberrypi.org>
|
|
|
|
Date: Thu, 11 Jul 2019 13:13:39 +0100
|
2019-12-23 12:42:55 +00:00
|
|
|
Subject: [PATCH] tty: amba-pl011: Make TX optimisation conditional
|
2019-07-31 16:23:26 +00:00
|
|
|
|
|
|
|
pl011_tx_chars takes a "from_irq" parameter to reduce the number of
|
|
|
|
register accesses. When from_irq is true the function assumes that the
|
|
|
|
FIFO is half empty and writes up to half a FIFO's worth of bytes
|
|
|
|
without polling the FIFO status register, the reasoning being that
|
|
|
|
the function is being called as a result of the TX interrupt being
|
|
|
|
raised. This logic would work were it not for the fact that
|
|
|
|
pl011_rx_chars, called from pl011_int before pl011_tx_chars, releases
|
|
|
|
the spinlock before calling tty_flip_buffer_push.
|
|
|
|
|
|
|
|
A user thread writing to the UART claims the spinlock and ultimately
|
|
|
|
calls pl011_tx_chars with from_irq set to false. This reverts to the
|
|
|
|
older logic that polls the FIFO status register before sending every
|
|
|
|
byte. If this happen on an SMP system during the section of the IRQ
|
|
|
|
handler where the spinlock has been released, then by the time the TX
|
|
|
|
interrupt handler is called, the FIFO may already be full, and any
|
|
|
|
further writes are likely to be lost.
|
|
|
|
|
|
|
|
The fix involves adding a per-port flag that is true iff running from
|
|
|
|
within the interrupt handler and the spinlock has not yet been released.
|
|
|
|
This flag is then used as the value for the from_irq parameter of
|
|
|
|
pl011_tx_chars, causing polling to be used in the unsafe case.
|
|
|
|
|
|
|
|
Fixes: 1e84d22322ce ("serial/amba-pl011: Refactor and simplify TX FIFO handling")
|
|
|
|
|
|
|
|
Signed-off-by: Phil Elwell <phil@raspberrypi.org>
|
|
|
|
---
|
|
|
|
drivers/tty/serial/amba-pl011.c | 7 ++++++-
|
|
|
|
1 file changed, 6 insertions(+), 1 deletion(-)
|
|
|
|
|
|
|
|
--- a/drivers/tty/serial/amba-pl011.c
|
|
|
|
+++ b/drivers/tty/serial/amba-pl011.c
|
|
|
|
@@ -270,6 +270,7 @@ struct uart_amba_port {
|
|
|
|
unsigned int old_cr; /* state during shutdown */
|
|
|
|
unsigned int fixed_baud; /* vendor-set fixed baud rate */
|
|
|
|
char type[12];
|
|
|
|
+ bool irq_locked; /* in irq, unreleased lock */
|
|
|
|
#ifdef CONFIG_DMA_ENGINE
|
|
|
|
/* DMA stuff */
|
|
|
|
bool using_tx_dma;
|
2019-12-18 15:38:57 +00:00
|
|
|
@@ -813,6 +814,7 @@ __acquires(&uap->port.lock)
|
|
|
|
if (!uap->using_tx_dma)
|
2019-07-31 16:23:26 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
+ uap->irq_locked = 0;
|
2019-12-18 15:38:57 +00:00
|
|
|
dmaengine_terminate_async(uap->dmatx.chan);
|
|
|
|
|
|
|
|
if (uap->dmatx.queued) {
|
|
|
|
@@ -939,6 +941,7 @@ static void pl011_dma_rx_chars(struct ua
|
2019-07-31 16:23:26 +00:00
|
|
|
fifotaken = pl011_fifo_to_tty(uap);
|
|
|
|
}
|
|
|
|
|
|
|
|
+ uap->irq_locked = 0;
|
|
|
|
spin_unlock(&uap->port.lock);
|
|
|
|
dev_vdbg(uap->port.dev,
|
|
|
|
"Took %d chars from DMA buffer and %d chars from the FIFO\n",
|
2019-12-18 15:38:57 +00:00
|
|
|
@@ -1347,6 +1350,7 @@ __acquires(&uap->port.lock)
|
2019-07-31 16:23:26 +00:00
|
|
|
{
|
|
|
|
pl011_fifo_to_tty(uap);
|
|
|
|
|
|
|
|
+ uap->irq_locked = 0;
|
|
|
|
spin_unlock(&uap->port.lock);
|
|
|
|
tty_flip_buffer_push(&uap->port.state->port);
|
|
|
|
/*
|
2019-12-18 15:38:57 +00:00
|
|
|
@@ -1482,6 +1486,7 @@ static irqreturn_t pl011_int(int irq, vo
|
2019-07-31 16:23:26 +00:00
|
|
|
int handled = 0;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&uap->port.lock, flags);
|
|
|
|
+ uap->irq_locked = 1;
|
|
|
|
status = pl011_read(uap, REG_RIS) & uap->im;
|
|
|
|
if (status) {
|
|
|
|
do {
|
2019-12-18 15:38:57 +00:00
|
|
|
@@ -1501,7 +1506,7 @@ static irqreturn_t pl011_int(int irq, vo
|
2019-07-31 16:23:26 +00:00
|
|
|
UART011_CTSMIS|UART011_RIMIS))
|
|
|
|
pl011_modem_status(uap);
|
|
|
|
if (status & UART011_TXIS)
|
|
|
|
- pl011_tx_chars(uap, true);
|
|
|
|
+ pl011_tx_chars(uap, uap->irq_locked);
|
|
|
|
|
|
|
|
if (pass_counter-- == 0)
|
|
|
|
break;
|