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 - 2026 Red Hat, Inc.