From: Frank Chang <frank.chang@sifive.com>
Currently, the SiFive UART raises an IRQ whenever:
1. ie.txwm is enabled.
2. ie.rxwm is enabled and the Rx FIFO is not empty.
It does not check the watermark thresholds set by software. However,
since commit [1] changed the SiFive UART character printing from
synchronous to asynchronous, Tx overflows may occur, causing characters
to be dropped when running Linux because:
1. The Linux SiFive UART driver sets the transmit watermark level to 1
[2], meaning a transmit watermark interrupt is raised whenever a
character is enqueued into the Tx FIFO.
2. Upon receiving a transmit watermark interrupt, the Linux driver
transfers up to a full Tx FIFO's worth of characters from the Linux
serial transmit buffer [3], without checking the txdata.full flag
before transferring multiple characters [4].
To fix this issue, we must honor the Tx/Rx watermark thresholds and
raise interrupts only when the Tx threshold is exceeded or the Rx
threshold is undercut.
[1] 53c1557b230986ab6320a58e1b2c26216ecd86d5
[2] https://github.com/torvalds/linux/blob/master/drivers/tty/serial/sifive.c#L1039
[3] https://github.com/torvalds/linux/blob/master/drivers/tty/serial/sifive.c#L538
[4] https://github.com/torvalds/linux/blob/master/drivers/tty/serial/sifive.c#L291
Signed-off-by: Frank Chang <frank.chang@sifive.com>
Signed-off-by: Emmanuel Blot <emmanuel.blot@sifive.com>
---
hw/char/sifive_uart.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/hw/char/sifive_uart.c b/hw/char/sifive_uart.c
index 9bc697a67b5..138c31fcabf 100644
--- a/hw/char/sifive_uart.c
+++ b/hw/char/sifive_uart.c
@@ -35,16 +35,17 @@
*/
/* Returns the state of the IP (interrupt pending) register */
-static uint64_t sifive_uart_ip(SiFiveUARTState *s)
+static uint32_t sifive_uart_ip(SiFiveUARTState *s)
{
- uint64_t ret = 0;
+ uint32_t ret = 0;
- uint64_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl);
- uint64_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl);
+ uint32_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl);
+ uint32_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl);
- if (txcnt != 0) {
+ if (fifo8_num_used(&s->tx_fifo) < txcnt) {
ret |= SIFIVE_UART_IP_TXWM;
}
+
if (s->rx_fifo_len > rxcnt) {
ret |= SIFIVE_UART_IP_RXWM;
}
@@ -55,15 +56,14 @@ static uint64_t sifive_uart_ip(SiFiveUARTState *s)
static void sifive_uart_update_irq(SiFiveUARTState *s)
{
int cond = 0;
- if ((s->ie & SIFIVE_UART_IE_TXWM) ||
- ((s->ie & SIFIVE_UART_IE_RXWM) && s->rx_fifo_len)) {
+ uint32_t ip = sifive_uart_ip(s);
+
+ if (((ip & SIFIVE_UART_IP_TXWM) && (s->ie & SIFIVE_UART_IE_TXWM)) ||
+ ((ip & SIFIVE_UART_IP_RXWM) && (s->ie & SIFIVE_UART_IE_RXWM))) {
cond = 1;
}
- if (cond) {
- qemu_irq_raise(s->irq);
- } else {
- qemu_irq_lower(s->irq);
- }
+
+ qemu_set_irq(s->irq, cond);
}
static gboolean sifive_uart_xmit(void *do_not_use, GIOCondition cond,
--
2.49.0
On Fri, Sep 12, 2025 at 2:08 AM <frank.chang@sifive.com> wrote: > > From: Frank Chang <frank.chang@sifive.com> > > Currently, the SiFive UART raises an IRQ whenever: > > 1. ie.txwm is enabled. > 2. ie.rxwm is enabled and the Rx FIFO is not empty. > > It does not check the watermark thresholds set by software. However, > since commit [1] changed the SiFive UART character printing from > synchronous to asynchronous, Tx overflows may occur, causing characters > to be dropped when running Linux because: > > 1. The Linux SiFive UART driver sets the transmit watermark level to 1 > [2], meaning a transmit watermark interrupt is raised whenever a > character is enqueued into the Tx FIFO. > 2. Upon receiving a transmit watermark interrupt, the Linux driver > transfers up to a full Tx FIFO's worth of characters from the Linux > serial transmit buffer [3], without checking the txdata.full flag > before transferring multiple characters [4]. > > To fix this issue, we must honor the Tx/Rx watermark thresholds and > raise interrupts only when the Tx threshold is exceeded or the Rx > threshold is undercut. > > [1] 53c1557b230986ab6320a58e1b2c26216ecd86d5 > [2] https://github.com/torvalds/linux/blob/master/drivers/tty/serial/sifive.c#L1039 > [3] https://github.com/torvalds/linux/blob/master/drivers/tty/serial/sifive.c#L538 > [4] https://github.com/torvalds/linux/blob/master/drivers/tty/serial/sifive.c#L291 > > Signed-off-by: Frank Chang <frank.chang@sifive.com> > Signed-off-by: Emmanuel Blot <emmanuel.blot@sifive.com> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Alistair > --- > hw/char/sifive_uart.c | 24 ++++++++++++------------ > 1 file changed, 12 insertions(+), 12 deletions(-) > > diff --git a/hw/char/sifive_uart.c b/hw/char/sifive_uart.c > index 9bc697a67b5..138c31fcabf 100644 > --- a/hw/char/sifive_uart.c > +++ b/hw/char/sifive_uart.c > @@ -35,16 +35,17 @@ > */ > > /* Returns the state of the IP (interrupt pending) register */ > -static uint64_t sifive_uart_ip(SiFiveUARTState *s) > +static uint32_t sifive_uart_ip(SiFiveUARTState *s) > { > - uint64_t ret = 0; > + uint32_t ret = 0; > > - uint64_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl); > - uint64_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl); > + uint32_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl); > + uint32_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl); > > - if (txcnt != 0) { > + if (fifo8_num_used(&s->tx_fifo) < txcnt) { > ret |= SIFIVE_UART_IP_TXWM; > } > + > if (s->rx_fifo_len > rxcnt) { > ret |= SIFIVE_UART_IP_RXWM; > } > @@ -55,15 +56,14 @@ static uint64_t sifive_uart_ip(SiFiveUARTState *s) > static void sifive_uart_update_irq(SiFiveUARTState *s) > { > int cond = 0; > - if ((s->ie & SIFIVE_UART_IE_TXWM) || > - ((s->ie & SIFIVE_UART_IE_RXWM) && s->rx_fifo_len)) { > + uint32_t ip = sifive_uart_ip(s); > + > + if (((ip & SIFIVE_UART_IP_TXWM) && (s->ie & SIFIVE_UART_IE_TXWM)) || > + ((ip & SIFIVE_UART_IP_RXWM) && (s->ie & SIFIVE_UART_IE_RXWM))) { > cond = 1; > } > - if (cond) { > - qemu_irq_raise(s->irq); > - } else { > - qemu_irq_lower(s->irq); > - } > + > + qemu_set_irq(s->irq, cond); > } > > static gboolean sifive_uart_xmit(void *do_not_use, GIOCondition cond, > -- > 2.49.0 > >
© 2016 - 2025 Red Hat, Inc.