[PATCH 1/4] hw/char: sifive_uart: Raise IRQ according to the Tx/Rx watermark thresholds

frank.chang@sifive.com posted 4 patches 2 weeks, 3 days ago
Maintainers: Alistair Francis <Alistair.Francis@wdc.com>, Palmer Dabbelt <palmer@dabbelt.com>, "Marc-André Lureau" <marcandre.lureau@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>
[PATCH 1/4] hw/char: sifive_uart: Raise IRQ according to the Tx/Rx watermark thresholds
Posted by frank.chang@sifive.com 2 weeks, 3 days ago
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
Re: [PATCH 1/4] hw/char: sifive_uart: Raise IRQ according to the Tx/Rx watermark thresholds
Posted by Alistair Francis 1 week, 6 days ago
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
>
>