Commit 7997b782f4 for qemu.org

commit 7997b782f454f455d6d2aa6a49253b315ce3a8bc
Author: Frank Chang <frank.chang@sifive.com>
Date:   Thu Mar 12 11:31:59 2026 +0800

    hw/char: sifive_uart: Sync txwm interrupt pending status after TX FIFO enqueue

    Currently, the txwm interrupt pending status is only updated when the
    asynchronous transmit handler runs. This can cause the txwm interrupt
    state to become unsynchronized between the SiFive UART and the
    interrupt controller.

    For example, when a txwm interrupt is raised, the corresponding APLIC
    pending bit is also set. However, if software later enqueues additional
    characters into the TX FIFO exceeding the transmit watermark, the
    APLIC pending bit may remain set because the txwm interrupt pending
    status is not updated at enqueue time.

    This issue has been observed on resource-constrained machines, where
    Linux reports spurious IRQ errors. In these cases, the asynchronous
    transmit handler is unable to drain the TX FIFO quickly enough to update
    the txwm pending status before software reads the ip register, which
    derives the txwm pending state directly from the actual number of
    characters in the TX FIFO.

    This commit fixes the issue by updating the txwm interrupt pending
    status immediately after enqueuing data into the TX FIFO, ensuring that
    the interrupt pending status between the SiFive UART and the interrupt
    controller remains synchronized.

    Signed-off-by: Frank Chang <frank.chang@sifive.com>
    Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
    Message-ID: <20260312033201.1619554-3-frank.chang@sifive.com>
    Signed-off-by: Alistair Francis <alistair.francis@wdc.com>

diff --git a/hw/char/sifive_uart.c b/hw/char/sifive_uart.c
index 3ce6a4ee76..ae71a15a2a 100644
--- a/hw/char/sifive_uart.c
+++ b/hw/char/sifive_uart.c
@@ -124,12 +124,20 @@ static void sifive_uart_trigger_tx_fifo(SiFiveUARTState *s)
 static void sifive_uart_write_tx_fifo(SiFiveUARTState *s, const uint8_t *buf,
                                       int size)
 {
+    uint32_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl);
+    bool update_irq = false;
+
     if (size > fifo8_num_free(&s->tx_fifo)) {
         size = fifo8_num_free(&s->tx_fifo);
         qemu_log_mask(LOG_GUEST_ERROR, "sifive_uart: TX FIFO overflow.\n");
     }

     if (size > 0) {
+        if (fifo8_num_used(&s->tx_fifo) < txcnt &&
+            (fifo8_num_used(&s->tx_fifo) + size) >= txcnt) {
+            update_irq = true;
+        }
+
         fifo8_push_all(&s->tx_fifo, buf, size);
     }

@@ -137,6 +145,14 @@ static void sifive_uart_write_tx_fifo(SiFiveUARTState *s, const uint8_t *buf,
         s->txfifo |= SIFIVE_UART_TXFIFO_FULL;
     }

+    /*
+     * Update txwm interrupt pending status when the number of entries
+     * in the transmit FIFO crosses or reaches the watermark.
+     */
+    if (update_irq) {
+        sifive_uart_update_irq(s);
+    }
+
     sifive_uart_trigger_tx_fifo(s);
 }