hw/char: sifive_uart: Raise IRQ according to the Tx/Rx watermark thresholds

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] 53c1557b23
[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>
Message-ID: <20250911160647.5710-2-frank.chang@sifive.com>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
This commit is contained in:
Frank Chang 2025-09-12 00:06:43 +08:00 committed by Alistair Francis
parent 7a9202eaeb
commit 191df34617

View file

@ -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,