All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RESEND v2 0/3] riscv: Add K230 DW 8250-compatible UART
@ 2026-07-25  3:52 WX Chen
  2026-07-25  3:52 ` [PATCH RESEND v2 1/3] hw/char: add " WX Chen
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: WX Chen @ 2026-07-25  3:52 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Marc-André Lureau, Chao Liu, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
	qemu-riscv, Fabiano Rosas, Laurent Vivier, WX Chen

This series adds a QEMU model for the K230 SoC DesignWare 8250-compatible
UART controller, wires it into the K230 SoC, and adds qtest coverage.

The implementation supports the registers and features required by the
Linux 8250_dw driver, providing an interactive shell over the serial port.

Changes since v1 [1]:
  - Split into a 3-patch series.
  - Fixed indentation of preprocessing macros.

Resend with a new sender address due to unexpected issues with the
previous email setup.

Testing: 8 qtest cases pass.  With the k230-boot-assets image from
https://github.com/zevorn/k230-boot-assets, the driver probes
successfully and the shell works bidirectionally over pty.

[1] https://lore.kernel.org/qemu-devel/tencent_FE5823EA5F7A9EDE163FEDCB3DBFBE1FD905@qq.com/

Signed-off-by: WX Chen <wxchen0913@gmail.com>
---
WX Chen (3):
      hw/char: add K230 DW 8250-compatible UART
      hw/riscv: k230: connect DW 8250 UART
      tests/qtest: add K230 UART test

 hw/char/Kconfig              |   3 +
 hw/char/k230_uart.c          | 840 +++++++++++++++++++++++++++++++++++++++++++
 hw/char/meson.build          |   1 +
 hw/riscv/k230.c              |  33 +-
 include/hw/char/k230_uart.h  | 180 ++++++++++
 include/hw/riscv/k230.h      |   6 +-
 tests/qtest/k230-uart-test.c | 514 ++++++++++++++++++++++++++
 tests/qtest/meson.build      |   2 +-
 8 files changed, 1565 insertions(+), 14 deletions(-)
---
base-commit: 30e8a06b64aa58a3990ba39cb5d09531e7d265e0
change-id: 20260725-feat-k230-uart-v2-1fbae069fb66

Best regards,
-- 
WX Chen <wxchen0913@gmail.com>



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH RESEND v2 1/3] hw/char: add K230 DW 8250-compatible UART
  2026-07-25  3:52 [PATCH RESEND v2 0/3] riscv: Add K230 DW 8250-compatible UART WX Chen
@ 2026-07-25  3:52 ` WX Chen
  2026-08-05 20:01   ` Daniel Henrique Barboza
  2026-07-25  3:52 ` [PATCH RESEND v2 2/3] hw/riscv: k230: connect DW 8250 UART WX Chen
  2026-07-25  3:52 ` [PATCH RESEND v2 3/3] tests/qtest: add K230 UART test WX Chen
  2 siblings, 1 reply; 9+ messages in thread
From: WX Chen @ 2026-07-25  3:52 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Marc-André Lureau, Chao Liu, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
	qemu-riscv, Fabiano Rosas, Laurent Vivier, WX Chen

Implement a K230 SoC DesignWare 8250-compatible UART controller model for
QEMU, capable of running the Linux 8250_dw driver and providing an
interactive shell.

Implemented:
  - Standard 16550 registers (RBR/THR/DLL, IER/DLH, IIR/FCR, LCR, MCR,
    LSR, MSR, SCR) and DesignWare-specific registers (USR, TFL, RFL, SRR,
    SRTS, SBCR, SDMAM, SFE, SRT, STET, HTX, CPR, UCV, CTR)
  - DLAB switching; 32-byte TX/RX FIFO; synchronous transmit with
    backpressure handling
  - Loopback mode; chardev BREAK routed to LSR.BI/FE via CHR_EVENT_BREAK
  - Four-level prioritized interrupt scheme (ELSI / RX Data / Timeout /
    THRE), with edge-triggered THRE
  - RX Character Timeout interrupt (IID=0xc) with 4-char-time timer that
    tracks the programmed divisor latch
  - Busy Detect interrupt (IID=0x7): LCR (and its shadow SBCR) writes
    while USR.BUSY=1 are rejected and raise IID=0x7, cleared by reading
    USR; USR.BUSY reflects TX-not-empty / RX-data-ready
  - Shadow registers alias their underlying fields
  - SRR (UR/RFR/XFR) self-clearing software reset

Not implemented:
  - RS485 transceiver control (TCR/DE_EN/RE_EN/DET/TAT)
  - 9-bit multidrop (LCR_EXT/RAR/TAR)
  - Fractional baud rate (DLF)
  - Auto Flow Control (MCR.AFCE)
  - IrDA SIR mode (MCR.SIRE)
  - Low-power divisor latch (LPDLL/LPDLH)
  - FIFO access test mode (FAR/TFR/RFW)
  - DMA Software Acknowledge (DMASA)

Signed-off-by: WX Chen <wxchen0913@gmail.com>
---
 hw/char/Kconfig             |   3 +
 hw/char/k230_uart.c         | 840 ++++++++++++++++++++++++++++++++++++++++++++
 hw/char/meson.build         |   1 +
 include/hw/char/k230_uart.h | 180 ++++++++++
 4 files changed, 1024 insertions(+)

diff --git a/hw/char/Kconfig b/hw/char/Kconfig
index 020c0a84bb6e5f11cc7d532a7999ade23505a1ba..bd000f7127e7881dabac191c81b2bcf66f549250 100644
--- a/hw/char/Kconfig
+++ b/hw/char/Kconfig
@@ -91,6 +91,9 @@ config GOLDFISH_TTY
 config SHAKTI_UART
     bool
 
+config K230_UART
+    bool
+
 config IP_OCTAL_232
     bool
     default y
diff --git a/hw/char/k230_uart.c b/hw/char/k230_uart.c
new file mode 100644
index 0000000000000000000000000000000000000000..36562b37463461e039f3096542fd142898dfbae6
--- /dev/null
+++ b/hw/char/k230_uart.c
@@ -0,0 +1,840 @@
+/*
+ * K230 UART
+ *
+ * Copyright (c) 2026 WX Chen <wxchen0913@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/char/k230_uart.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties-system.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qemu/host-utils.h"
+#include "system/memory.h"
+
+/* Serial clock from the K230 DT "clock-frequency" property (50 MHz). */
+#define K230_UART_SCLK_HZ   50000000ull
+
+static void k230_uart_xmit(K230UartState *s);
+static void k230_uart_update_all(K230UartState *s);
+static uint64_t k230_uart_read_rbr(K230UartState *s);
+static void k230_uart_push_rx_byte(K230UartState *s, uint8_t ch);
+static void k230_uart_reset_hold(Object *obj, ResetType type);
+
+/* ---- interrupt line ---- */
+
+/*
+ * RX FIFO trigger level. When rx_count reaches this,
+ * the RX Data Available interrupt (IID=0x4) fires.
+ */
+static uint32_t k230_uart_rx_trigger_level(const K230UartState *s)
+{
+    if (!FIELD_EX32(s->fcr, FCR, FIFOE)) {
+        return 1;  /* non-FIFO mode: 1 byte */
+    }
+    switch (FIELD_EX32(s->fcr, FCR, RT)) {
+    case 0:  return 1;
+    case 1:  return K230_UART_FIFO_DEPTH / 4;
+    case 2:  return K230_UART_FIFO_DEPTH / 2;
+    case 3:  return K230_UART_FIFO_DEPTH - 2;
+    default: return 1;
+    }
+}
+
+static void k230_uart_update_irq(K230UartState *s)
+{
+    bool irq = false;
+    uint32_t rx_itl = k230_uart_rx_trigger_level(s);
+
+    /* RX trigger level > 1 */
+    if (FIELD_EX32(s->ier, IER, ERBFI) &&
+        (s->rx_count >= rx_itl || s->timeout_ipending)) {
+        irq = true;
+    }
+    /* Transmit Holding Register Empty Interrupt */
+    if (FIELD_EX32(s->ier, IER, ETBEI) && s->thr_ipending) {
+        irq = true;
+    }
+    /* Receiver Line Status Interrupt */
+    if (FIELD_EX32(s->ier, IER, ELSI) &&
+        (FIELD_EX32(s->lsr, LSR, OE) || FIELD_EX32(s->lsr, LSR, PE) ||
+         FIELD_EX32(s->lsr, LSR, FE) || FIELD_EX32(s->lsr, LSR, BI))) {
+        irq = true;
+    }
+    /* Busy detect interrupt (DW-specific, no IER gate). */
+    if (s->busy_ipending) {
+        irq = true;
+    }
+    qemu_set_irq(s->irq, irq ? 1 : 0);
+}
+
+/* ---- FIFO reset ---- */
+
+static void k230_uart_fifo_reset(K230UartState *s, bool rx, bool tx)
+{
+    if (rx) {
+        s->rx_head = s->rx_tail = s->rx_count = 0;
+        s->lsr = FIELD_DP32(s->lsr, LSR, DR, 0);
+        s->timeout_ipending = 0;
+        timer_del(&s->rx_timeout);
+    }
+    if (tx) {
+        s->tx_head = s->tx_tail = s->tx_count = 0;
+        s->lsr = FIELD_DP32(s->lsr, LSR, THRE, 1);
+        s->lsr = FIELD_DP32(s->lsr, LSR, TEMT, 1);
+        s->thr_ipending = 1;
+    }
+    k230_uart_update_all(s);
+}
+
+/* ---- transmit ---- */
+
+static gboolean k230_uart_chr_can_write(void *do_not_use, GIOCondition cond,
+                                        void *opaque)
+{
+    K230UartState *s = K230_UART(opaque);
+    if (!s->htx) {
+        k230_uart_xmit(s);
+    }
+    return G_SOURCE_REMOVE;
+}
+
+/* ---- baud / char-transmit time ---- */
+
+/*
+ * Recompute the per-character transmit time from the current divisor latch:
+ *
+ *   baud    = sclk / (16 * divisor)
+ *   char_ns = 10 * 16 * divisor * 1e9 / sclk     (10 = 8N1 start+data+stop)
+ *
+ * Drives the RX timeout interrupt (IID=0xc), which fires after 4 char times.
+ * divisor == 0 means "unprogrammed": keep the previous (reset default) value.
+ */
+static void k230_uart_update_char_time(K230UartState *s)
+{
+    uint32_t divisor = ((uint32_t)s->dlh << 8) | s->dll;
+    if (divisor == 0) {
+        return;
+    }
+    s->char_transmit_time = muldiv64(10ull * 16 * divisor,
+                                     NANOSECONDS_PER_SECOND,
+                                     K230_UART_SCLK_HZ);
+}
+
+/* Send all characters in THR or TX-FIFO into char backend */
+static void k230_uart_xmit(K230UartState *s)
+{
+    int ret;
+
+    if (s->tx_count == 0 || s->htx) {
+        return;
+    }
+
+    while (s->tx_count > 0) {
+        uint8_t ch = (uint8_t)s->tx_fifo[s->tx_tail];
+
+        if (FIELD_EX32(s->mcr, MCR, LOOPBACK)) {
+            /* Go to RX fifo */
+            k230_uart_push_rx_byte(s, ch);
+            ret = 1;
+        } else {
+            /* Go to char backend */
+            ret = qemu_chr_fe_write(&s->chr, &ch, 1);
+        }
+        if (ret < 0) {
+            /* Wait for backend to be accessible then call the callback */
+            qemu_chr_fe_add_watch(&s->chr, G_IO_OUT,
+                                  k230_uart_chr_can_write, s);
+            break;
+        }
+
+        s->tx_tail = (s->tx_tail + 1) % K230_UART_FIFO_DEPTH;
+        s->tx_count--;
+    }
+
+    s->lsr = FIELD_DP32(s->lsr, LSR, TEMT, (s->tx_count == 0) ? 1 : 0);
+    k230_uart_update_all(s);
+}
+
+/* ---- RX timeout ---- */
+
+/* Character timeout callback */
+static void k230_uart_rx_timeout(void *opaque)
+{
+    K230UartState *s = K230_UART(opaque);
+    if (s->rx_count > 0) {
+        s->timeout_ipending = 1;
+        k230_uart_update_all(s);
+    }
+}
+
+/* ---- receive ---- */
+
+/* Push a byte to rxfifo(when FIFO_EN is set) or RBR(FIFO_EN not set). */
+static void k230_uart_push_rx_byte(K230UartState *s, uint8_t ch)
+{
+    bool fifo_en = FIELD_EX32(s->fcr, FCR, FIFOE);
+    uint16_t capacity = fifo_en ? K230_UART_FIFO_DEPTH : 1;
+
+    if (s->rx_count < capacity) {
+        s->rx_fifo[s->rx_head] = ch;
+        s->rx_head = (s->rx_head + 1) % K230_UART_FIFO_DEPTH;
+        s->rx_count++;
+        s->lsr = FIELD_DP32(s->lsr, LSR, DR, 1);
+        timer_mod(&s->rx_timeout,
+                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+                  + 4 * s->char_transmit_time);
+    } else {
+        s->lsr = FIELD_DP32(s->lsr, LSR, OE, 1);
+    }
+    k230_uart_update_all(s);
+}
+
+/* ---- derived register recomputation ---- */
+
+static void k230_uart_update_thre(K230UartState *s)
+{
+    bool should_set = (s->tx_count == 0);
+#if K230_UART_THRE_MODE
+    if (FIELD_EX32(s->ier, IER, PTIME)
+        && FIELD_EX32(s->fcr, FCR, FIFOE)) {
+        uint8_t tet = FIELD_EX32(s->fcr, FCR, TET);
+        uint16_t threshold;
+        switch (tet) {
+        case 0x0:
+            threshold = 0;
+            break;
+        case 0x1:
+            threshold = 2;
+            break;
+        case 0x2:
+            threshold = K230_UART_FIFO_DEPTH / 4;
+            break;
+        case 0x3:
+            threshold = K230_UART_FIFO_DEPTH / 2;
+            break;
+        default:
+            threshold = 0;
+            break;
+        }
+        should_set = (s->tx_count <= threshold);
+    }
+#endif
+
+    uint8_t old_thre = FIELD_EX32(s->lsr, LSR, THRE);
+    s->lsr = FIELD_DP32(s->lsr, LSR, THRE, should_set ? 1 : 0);
+
+    /* Edge trigger */
+    if (should_set && !old_thre) {
+        s->thr_ipending = 1;
+    }
+}
+
+static bool k230_uart_is_busy(const K230UartState *s)
+{
+    return !FIELD_EX32(s->lsr, LSR, TEMT) || FIELD_EX32(s->lsr, LSR, DR);
+}
+
+static void k230_uart_update_usr(K230UartState *s)
+{
+    bool fifo_en = FIELD_EX32(s->fcr, FCR, FIFOE);
+    uint16_t capacity = fifo_en ? K230_UART_FIFO_DEPTH : 1;
+
+    s->usr = 0;
+#if !K230_UART_16550_COMPATIBLE
+    if (k230_uart_is_busy(s)) {
+        s->usr = FIELD_DP32(s->usr, USR, BUSY, 1);
+    }
+#endif
+#if K230_UART_FIFO_STAT && K230_UART_FIFO_MODE
+    s->usr = FIELD_DP32(s->usr, USR, TFNF, s->tx_count < capacity ? 1 : 0);
+    s->usr = FIELD_DP32(s->usr, USR, TFE, s->tx_count == 0 ? 1 : 0);
+    s->usr = FIELD_DP32(s->usr, USR, RFNE, s->rx_count != 0 ? 1 : 0);
+    s->usr = FIELD_DP32(s->usr, USR, RFF, s->rx_count == capacity ? 1 : 0);
+#endif
+}
+
+static void k230_uart_update_iir(K230UartState *s)
+{
+    s->iir = 0;
+    /* Indicate whether FIFO is enabled or not */
+    if (FIELD_EX32(s->fcr, FCR, FIFOE)) {
+        s->iir = FIELD_DP32(s->iir, IIR, FIFOSE, 0x3);
+    }
+
+    if (FIELD_EX32(s->ier, IER, ELSI) &&
+        (FIELD_EX32(s->lsr, LSR, OE) || FIELD_EX32(s->lsr, LSR, PE) ||
+         FIELD_EX32(s->lsr, LSR, FE) || FIELD_EX32(s->lsr, LSR, BI))) {
+        /* Receiver Line Status Interrupt */
+        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x6);
+    } else if (FIELD_EX32(s->ier, IER, ERBFI) &&
+               (s->rx_count >= k230_uart_rx_trigger_level(s))) {
+        /* Receiver Data Available Interrupt */
+        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x4);
+    } else if (FIELD_EX32(s->ier, IER, ERBFI) && s->timeout_ipending) {
+        /* Character timeout interrupt */
+        s->iir = FIELD_DP32(s->iir, IIR, IID, 0xc);
+    } else if (FIELD_EX32(s->ier, IER, ETBEI) && s->thr_ipending) {
+        /* THR Empty Interrupt */
+        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x2);
+    } else if (s->busy_ipending) {
+        /* Busy detect: LCR written while USR.BUSY=1 (DW-specific) */
+        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x7);
+    } else {
+        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x1);
+    }
+}
+
+
+static void k230_uart_update_msr(K230UartState *s)
+{
+    uint8_t old_status = s->msr & 0xf0;
+    uint8_t new_status;
+
+    if (FIELD_EX32(s->mcr, MCR, LOOPBACK)) {
+        /*
+         * Standard 16550 loopback mapping:
+         * DTR -> DSR, RTS -> CTS, OUT1 -> RI, OUT2 -> DCD
+         */
+        new_status = 0;
+        if (FIELD_EX32(s->mcr, MCR, RTS)) {
+            new_status |= 0x10; /* CTS */
+        }
+        if (FIELD_EX32(s->mcr, MCR, DTR)) {
+            new_status |= 0x20; /* DSR */
+        }
+        if (FIELD_EX32(s->mcr, MCR, OUT1)) {
+            new_status |= 0x40; /* RI  */
+        }
+        if (FIELD_EX32(s->mcr, MCR, OUT2)) {
+            new_status |= 0x80; /* DCD */
+        }
+    } else {
+        /* No real modem: report CTS/DSR/DCD as active (ready) */
+        new_status = 0xb0; /* CTS | DSR | DCD */
+    }
+
+    /* Accumulate delta bits on status changes */
+    uint8_t deltas = s->msr & 0x0f;
+    uint8_t changes = old_status ^ new_status;
+    if (changes & 0x10) {
+        deltas |= 0x01; /* DCTS */
+    }
+    if (changes & 0x20) {
+        deltas |= 0x02; /* DDSR */
+    }
+    if ((old_status & 0x40) && !(new_status & 0x40)) {
+        deltas |= 0x04; /* TERI */
+    }
+    if (changes & 0x80) {
+        deltas |= 0x08; /* DDCD */
+    }
+
+    s->msr = new_status | deltas;
+}
+
+static void k230_uart_update_all(K230UartState *s)
+{
+    k230_uart_update_thre(s);
+    k230_uart_update_usr(s);
+    k230_uart_update_iir(s);
+    k230_uart_update_irq(s);
+}
+
+/* ---- MMIO read ---- */
+
+static uint64_t k230_uart_read_rbr(K230UartState *s)
+{
+    uint64_t ret = 0;
+
+    if (s->rx_count > 0) {
+        ret = s->rx_fifo[s->rx_tail];
+        s->rx_tail = (s->rx_tail + 1) % K230_UART_FIFO_DEPTH;
+        s->rx_count--;
+        s->timeout_ipending = 0;
+        if (s->rx_count == 0) {
+            s->lsr = FIELD_DP32(s->lsr, LSR, DR, 0);
+            timer_del(&s->rx_timeout);
+        } else {
+            timer_mod(&s->rx_timeout,
+                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+                      + 4 * s->char_transmit_time);
+        }
+        qemu_chr_fe_accept_input(&s->chr);
+    }
+
+    if (!FIELD_EX32(s->ier, IER, ELCOLR)) {
+        s->lsr = FIELD_DP32(s->lsr, LSR, OE, 0);
+        s->lsr = FIELD_DP32(s->lsr, LSR, PE, 0);
+        s->lsr = FIELD_DP32(s->lsr, LSR, FE, 0);
+        s->lsr = FIELD_DP32(s->lsr, LSR, BI, 0);
+    }
+
+    k230_uart_update_all(s);
+    return ret;
+}
+
+static uint64_t k230_uart_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    K230UartState *s = K230_UART(opaque);
+    uint64_t ret = 0;
+
+    switch (addr >> 2) {
+    case R_RBR_DLL_THR:
+        if (FIELD_EX32(s->lcr, LCR, DLAB)) {
+            /* DLL accessible only when not busy. */
+            ret = (K230_UART_16550_COMPATIBLE || !k230_uart_is_busy(s))
+                  ? s->dll : 0;
+        } else {
+            ret = k230_uart_read_rbr(s);
+        }
+        break;
+    case R_IER_DLH:
+        if (FIELD_EX32(s->lcr, LCR, DLAB)) {
+            /* DLH accessible only when not busy. */
+            ret = (K230_UART_16550_COMPATIBLE || !k230_uart_is_busy(s))
+                  ? s->dlh : 0;
+        } else {
+            /* IER */
+            ret = s->ier;
+        }
+        break;
+    case R_IIR:
+        ret = s->iir;
+        if (FIELD_EX32(s->iir, IIR, IID) == 0x2) {
+            s->thr_ipending = 0;
+            k230_uart_update_all(s);
+        }
+        break;
+    case R_LCR:
+        ret = s->lcr;
+        break;
+    case R_LSR:
+        ret = s->lsr;
+        /* Clear OE PE FE and Break Interrupt status bits */
+        s->lsr = FIELD_DP32(s->lsr, LSR, OE, 0);
+        s->lsr = FIELD_DP32(s->lsr, LSR, PE, 0);
+        s->lsr = FIELD_DP32(s->lsr, LSR, FE, 0);
+        s->lsr = FIELD_DP32(s->lsr, LSR, BI, 0);
+        k230_uart_update_all(s);
+        break;
+    case R_MCR:
+        ret = s->mcr;
+        break;
+    case R_MSR:
+        ret = s->msr;
+        s->msr &= 0xf0; /* clear delta bits on read */
+        break;
+    case R_SCR:
+        ret = s->scr;
+        break;
+
+    /* Shadow registers: aliases of standard registers. */
+    case R_SRTS:
+        ret = FIELD_EX32(s->mcr, MCR, RTS);
+        break;
+    case R_SBCR:
+        ret = FIELD_EX32(s->lcr, LCR, BC);
+        break;
+    case R_SDMAM:
+        ret = FIELD_EX32(s->fcr, FCR, DMAM);
+        break;
+    case R_SFE:
+        ret = FIELD_EX32(s->fcr, FCR, FIFOE);
+        break;
+    case R_SRT:
+        ret = FIELD_EX32(s->fcr, FCR, RT);
+        break;
+    case R_STET:
+        ret = FIELD_EX32(s->fcr, FCR, TET);
+        break;
+    case R_HTX:
+        ret = s->htx;
+        break;
+
+    case R_CPR:
+        ret = (K230_UART_APB_DATA_WIDTH          << 0)   |
+                   (K230_UART_AFCE_MODE               << 4)   |
+                   (K230_UART_THRE_MODE                << 5)   |
+                   (K230_UART_SIR_MODE                 << 6)   |
+                   (K230_UART_SIR_LP_MODE              << 7)   |
+                   (K230_UART_ADDITIONAL_FEATURES      << 8)   |
+                   (K230_UART_FIFO_ACCESS              << 9)   |
+                   (K230_UART_FIFO_STAT                << 10)  |
+                   (K230_UART_SHADOW                   << 11)  |
+                   (K230_UART_ADD_ENCODED_PARAMS       << 12)  |
+                   (K230_UART_DMA_EXTRA                << 13)  |
+                   (K230_UART_FIFO_MODE                << 16);
+        break;
+    case R_UCV:
+        /* Version */
+        ret = 0x342e3061;
+        break;
+    case R_DLF:
+        ret = 0;
+        break;
+    case R_USR:
+        ret = s->usr;
+        /* Reading USR clears the busy-detect interrupt. */
+        if (s->busy_ipending) {
+            s->busy_ipending = 0;
+            k230_uart_update_all(s);
+        }
+        break;
+    case R_TFL:
+        /* Transmit FIFO Level: number of bytes in TX FIFO. */
+        ret = s->tx_count;
+        break;
+    case R_RFL:
+        /* Receive FIFO Level: number of bytes in RX FIFO. */
+        ret = s->rx_count;
+        break;
+    case R_CTR:
+        /* Component Type Register: fixed ID. */
+        ret = K230_UART_CTR_VALUE;
+        break;
+    default:
+        ret = 0;
+        break;
+    }
+    return ret;
+}
+
+/* ---- MMIO write ---- */
+
+static void k230_uart_write(void *opaque, hwaddr addr,
+                                uint64_t val64, unsigned int size)
+{
+    K230UartState *s = K230_UART(opaque);
+    uint32_t val = (uint32_t)val64;
+    uint32_t offset = addr >> 2;
+
+    switch (offset) {
+    case R_RBR_DLL_THR:
+        if (FIELD_EX32(s->lcr, LCR, DLAB)) {
+            /* DLL writable only when not busy. */
+            if (K230_UART_16550_COMPATIBLE || !k230_uart_is_busy(s)) {
+                s->dll = val & 0xff;
+                k230_uart_update_char_time(s);
+            }
+        } else {
+            bool fifo_en = FIELD_EX32(s->fcr, FCR, FIFOE);
+            uint16_t capacity = fifo_en ? K230_UART_FIFO_DEPTH : 1;
+            if (s->tx_count < capacity) {
+                s->tx_fifo[s->tx_head] = val & 0xff;
+                s->tx_head = (s->tx_head + 1) % K230_UART_FIFO_DEPTH;
+                s->tx_count++;
+            } else if (!fifo_en) {
+                s->tx_fifo[s->tx_tail] = val & 0xff;
+            }
+            s->lsr = FIELD_DP32(s->lsr, LSR, THRE, 0);
+            s->thr_ipending = 0;
+            s->lsr = FIELD_DP32(s->lsr, LSR, TEMT, 0);
+            k230_uart_xmit(s);
+        }
+        break;
+    case R_IER_DLH:
+        if (FIELD_EX32(s->lcr, LCR, DLAB)) {
+            /* DLH writable only when not busy. */
+            if (K230_UART_16550_COMPATIBLE || !k230_uart_is_busy(s)) {
+                s->dlh = val & 0xff;
+                k230_uart_update_char_time(s);
+            }
+        } else {
+            uint8_t old_etbei = FIELD_EX32(s->ier, IER, ETBEI);
+            s->ier = FIELD_DP32(s->ier, IER, ERBFI,
+                                FIELD_EX32(val, IER, ERBFI));
+            s->ier = FIELD_DP32(s->ier, IER, ETBEI,
+                                FIELD_EX32(val, IER, ETBEI));
+            s->ier = FIELD_DP32(s->ier, IER, ELSI,
+                                FIELD_EX32(val, IER, ELSI));
+            s->ier = FIELD_DP32(s->ier, IER, EDSSI,
+                                FIELD_EX32(val, IER, EDSSI));
+            s->ier = FIELD_DP32(s->ier, IER, ELCOLR,
+                                FIELD_EX32(val, IER, ELCOLR));
+#if K230_UART_THRE_MODE
+            s->ier = FIELD_DP32(s->ier, IER, PTIME,
+                                FIELD_EX32(val, IER, PTIME));
+#endif
+            if (!old_etbei && FIELD_EX32(s->ier, IER, ETBEI)
+                && FIELD_EX32(s->lsr, LSR, THRE)) {
+                s->thr_ipending = 1;
+            }
+        }
+        break;
+    case R_FCR:
+        if ((val ^ s->fcr) & R_FCR_FIFOE_MASK) {
+            val |= R_FCR_RFIFOR_MASK | R_FCR_XFIFOR_MASK;
+        }
+        s->fcr = FIELD_DP32(s->fcr, FCR, FIFOE,
+                            FIELD_EX32(val, FCR, FIFOE));
+        s->fcr = FIELD_DP32(s->fcr, FCR, DMAM,
+                            FIELD_EX32(val, FCR, DMAM));
+        s->fcr = FIELD_DP32(s->fcr, FCR, RT,
+                            FIELD_EX32(val, FCR, RT));
+#if K230_UART_THRE_MODE
+        s->fcr = FIELD_DP32(s->fcr, FCR, TET,
+                            FIELD_EX32(val, FCR, TET));
+#endif
+        if (FIELD_EX32(val, FCR, RFIFOR)) {
+            k230_uart_fifo_reset(s, true, false);
+        }
+        if (FIELD_EX32(val, FCR, XFIFOR)) {
+            k230_uart_fifo_reset(s, false, true);
+        }
+        break;
+    case R_LCR:
+        /*
+         * When UART_16550_COMPATIBLE == NO (the K230 case),
+         * LCR is writable only when USR.BUSY == 0.  A write while busy is
+         * ignored and triggers the busy-detect interrupt (IID=0x7), which is
+         * cleared by reading USR.  The 8250_dw driver detects this via
+         * dw8250_check_lcr() and retries after draining the FIFOs.
+         */
+        if (!K230_UART_16550_COMPATIBLE && k230_uart_is_busy(s)) {
+            s->busy_ipending = 1;
+        } else {
+            s->lcr = val & 0xff;
+        }
+        break;
+    case R_MCR:
+        /*
+         * K230 advertises AFCE_MODE=0 and SIR_MODE=0 in CPR, so MCR[5:6]
+         * (AFCE/SIRE) are read-only 0.  MCR[7] is reserved and also 0.
+         */
+        s->mcr = val & 0x1f;
+        k230_uart_update_msr(s);
+        break;
+    case R_SCR:
+        s->scr = val & 0xff;
+        break;
+
+    /*
+     * Shadow registers: aliases of standard register fields.
+     * They let software update a single bit without read-modify-write on the
+     * original register.  SBCR shadows LCR[6] (BC), which is gated by the
+     * busy-detect logic just like a direct LCR write.
+     */
+    case R_SRTS:
+        s->mcr = FIELD_DP32(s->mcr, MCR, RTS, val & 0x1);
+        k230_uart_update_msr(s);
+        break;
+    case R_SBCR:
+        if (!K230_UART_16550_COMPATIBLE && k230_uart_is_busy(s)) {
+            s->busy_ipending = 1;
+        } else {
+            s->lcr = FIELD_DP32(s->lcr, LCR, BC, val & 0x1);
+        }
+        break;
+    case R_SDMAM:
+        s->fcr = FIELD_DP32(s->fcr, FCR, DMAM, val & 0x1);
+        break;
+    case R_SFE:
+        /* Shadow of FCR[0]; changing FIFOE resets both FIFOs */
+        if ((s->fcr ^ val) & R_FCR_FIFOE_MASK) {
+            k230_uart_fifo_reset(s, true, true);
+        }
+        s->fcr = FIELD_DP32(s->fcr, FCR, FIFOE, val & 0x1);
+        break;
+    case R_SRT:
+        s->fcr = FIELD_DP32(s->fcr, FCR, RT, val & 0x3);
+        break;
+    case R_STET:
+#if K230_UART_THRE_MODE
+        s->fcr = FIELD_DP32(s->fcr, FCR, TET, val & 0x3);
+#endif
+        break;
+    case R_HTX:
+        s->htx = val & 0x1;
+        /* Clearing HTX resumes transmission of any buffered TX data. */
+        if (!s->htx) {
+            k230_uart_xmit(s);
+        }
+        break;
+
+    case R_SRR:
+        if (FIELD_EX32(val, SRR, UR)) {
+            device_cold_reset(DEVICE(s));
+            return;
+        }
+        if (FIELD_EX32(val, SRR, RFR)) {
+            k230_uart_fifo_reset(s, true, false);
+        }
+        if (FIELD_EX32(val, SRR, XFR)) {
+            k230_uart_fifo_reset(s, false, true);
+        }
+        break;
+
+    default:
+        break;
+    }
+    k230_uart_update_all(s);
+}
+
+/* ---- device lifecycle ---- */
+
+static const MemoryRegionOps k230_uart_ops = {
+    .read = k230_uart_read,
+    .write = k230_uart_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+};
+
+static void k230_uart_init(Object *obj)
+{
+    K230UartState *s = K230_UART(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    memory_region_init_io(&s->mmio, OBJECT(s), &k230_uart_ops, s,
+                          TYPE_K230_UART, 0x100);
+    sysbus_init_mmio(sbd, &s->mmio);
+    sysbus_init_irq(sbd, &s->irq);
+}
+
+static int k230_uart_can_receive(void *opaque)
+{
+    K230UartState *s = K230_UART(opaque);
+    return K230_UART_FIFO_DEPTH - s->rx_count;
+}
+
+static void k230_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+    K230UartState *s = K230_UART(opaque);
+    for (int i = 0; i < size; i++) {
+        k230_uart_push_rx_byte(s, buf[i]);
+    }
+}
+
+static void k230_uart_event(void *opaque, QEMUChrEvent event)
+{
+    K230UartState *s = K230_UART(opaque);
+
+    if (event == CHR_EVENT_BREAK) {
+        k230_uart_push_rx_byte(s, 0);
+        s->lsr = FIELD_DP32(s->lsr, LSR, BI, 1);
+        s->lsr = FIELD_DP32(s->lsr, LSR, FE, 1);
+        k230_uart_update_all(s);
+    }
+}
+
+static void k230_uart_reset_hold(Object *obj, ResetType type)
+{
+    K230UartState *s = K230_UART(obj);
+    s->lcr = 0;
+    s->fcr = 0;
+    s->ier = 0;
+    s->mcr = 0;
+    s->scr = 0;
+    s->dll = 0;
+    s->dlh = 0;
+    s->lsr = 0x60;
+    s->msr = 0xb0;
+
+    s->rx_head = s->rx_tail = s->rx_count = 0;
+    s->tx_head = s->tx_tail = s->tx_count = 0;
+    s->htx = 0;
+
+    qemu_set_irq(s->irq, 0);
+
+    s->thr_ipending = 0;
+    s->timeout_ipending = 0;
+    s->busy_ipending = 0;
+    timer_del(&s->rx_timeout);
+    /* Default to 115200 baud until the driver programs the divisor latch. */
+    s->char_transmit_time = (NANOSECONDS_PER_SECOND / 115200) * 10;
+
+    k230_uart_update_all(s);
+}
+
+static void k230_uart_realize(DeviceState *dev, Error **errp)
+{
+    K230UartState *s = K230_UART(dev);
+    qemu_chr_fe_set_handlers(&s->chr, k230_uart_can_receive,
+                             k230_uart_receive, k230_uart_event,
+                             NULL, s, NULL, true);
+
+    timer_init_ns(&s->rx_timeout, QEMU_CLOCK_VIRTUAL,
+                  k230_uart_rx_timeout, s);
+}
+
+static int k230_uart_post_load(void *opaque, int version_id)
+{
+    K230UartState *s = K230_UART(opaque);
+
+    /*
+     * iir and usr are derived from the saved state; recompute them after
+     * migration so the device is consistent.
+     */
+    k230_uart_update_all(s);
+    return 0;
+}
+
+static const VMStateDescription vmstate_k230_uart = {
+    .name = "k230.uart",
+    .version_id = 2,
+    .minimum_version_id = 1,
+    .post_load = k230_uart_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT8(lcr,   K230UartState),
+        VMSTATE_UINT8(fcr,   K230UartState),
+        VMSTATE_UINT8(ier,   K230UartState),
+        VMSTATE_UINT8(dll,   K230UartState),
+        VMSTATE_UINT8(dlh,   K230UartState),
+        VMSTATE_UINT8(mcr,   K230UartState),
+        VMSTATE_UINT8(lsr,   K230UartState),
+        VMSTATE_UINT8(msr,   K230UartState),
+        VMSTATE_UINT8(scr,   K230UartState),
+        VMSTATE_UINT8(htx,   K230UartState),
+        VMSTATE_UINT16_ARRAY(rx_fifo, K230UartState,
+                             K230_UART_FIFO_DEPTH),
+        VMSTATE_UINT16_ARRAY(tx_fifo, K230UartState,
+                             K230_UART_FIFO_DEPTH),
+        VMSTATE_UINT32(rx_head, K230UartState),
+        VMSTATE_UINT32(rx_tail, K230UartState),
+        VMSTATE_UINT32(rx_count, K230UartState),
+        VMSTATE_UINT32(tx_head, K230UartState),
+        VMSTATE_UINT32(tx_tail, K230UartState),
+        VMSTATE_UINT32(tx_count, K230UartState),
+        VMSTATE_UINT8(thr_ipending, K230UartState),
+        VMSTATE_UINT8(timeout_ipending, K230UartState),
+        VMSTATE_UINT8(busy_ipending, K230UartState),
+        VMSTATE_UINT64(char_transmit_time, K230UartState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const Property k230_uart_properties[] = {
+    DEFINE_PROP_CHR("chardev", K230UartState, chr),
+};
+
+static void k230_uart_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->realize = k230_uart_realize;
+    rc->phases.hold = k230_uart_reset_hold;
+    dc->vmsd = &vmstate_k230_uart;
+    dc->desc = "K230 UART (16550-compatible)";
+    device_class_set_props(dc, k230_uart_properties);
+}
+
+static const TypeInfo k230_uart_info = {
+    .name = TYPE_K230_UART,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(K230UartState),
+    .instance_init = k230_uart_init,
+    .class_init = k230_uart_class_init
+};
+
+static void k230_uart_register_types(void)
+{
+    type_register_static(&k230_uart_info);
+}
+
+type_init(k230_uart_register_types)
diff --git a/hw/char/meson.build b/hw/char/meson.build
index fc3d7ee506fcf8eb1219f6af9689fd90573861c9..23d8ede3f031f80d1b519bf49beac9d23502e2cb 100644
--- a/hw/char/meson.build
+++ b/hw/char/meson.build
@@ -38,6 +38,7 @@ system_ss.add(when: 'CONFIG_STM32L4X5_USART', if_true: files('stm32l4x5_usart.c'
 system_ss.add(when: 'CONFIG_MCHP_PFSOC_MMUART', if_true: files('mchp_pfsoc_mmuart.c'))
 system_ss.add(when: 'CONFIG_HTIF', if_true: files('riscv_htif.c'))
 system_ss.add(when: 'CONFIG_GOLDFISH_TTY', if_true: files('goldfish_tty.c'))
+system_ss.add(when: 'CONFIG_K230', if_true: files('k230_uart.c'))
 
 specific_ss.add(when: 'CONFIG_TERMINAL3270', if_true: files('terminal3270.c'))
 specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_vty.c'))
diff --git a/include/hw/char/k230_uart.h b/include/hw/char/k230_uart.h
new file mode 100644
index 0000000000000000000000000000000000000000..09407f01bcd00ea6fdc05d054c9a53d4cee3cb98
--- /dev/null
+++ b/include/hw/char/k230_uart.h
@@ -0,0 +1,180 @@
+/*
+ * K230 UART device
+ *
+ * K230 Technical Reference Manual V0.3.1 (2024-11-18):
+ * https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf
+ *
+ * Register semantics cross-checked against the SDK Linux driver
+ * src/little/linux/drivers/tty/serial/8250/8250_dw.c in
+ * https://github.com/kendryte/k230_sdk (compatible "snps,dw-apb-uart").
+ *
+ * Copyright (c) 2026 WX Chen <wxchen0913@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_K230_UART_H
+#define HW_K230_UART_H
+
+#include "hw/core/sysbus.h"
+#include "chardev/char-fe.h"
+#include "hw/core/registerfields.h"
+#include "qom/object.h"
+
+REG32(RBR_DLL_THR, 0x00)
+REG32(IER_DLH, 0x04)
+REG32(IER, 0x04)
+    FIELD(IER, ERBFI, 0, 1)
+    FIELD(IER, ETBEI, 1, 1)
+    FIELD(IER, ELSI, 2, 1)
+    FIELD(IER, EDSSI, 3, 1)
+    FIELD(IER, ELCOLR, 4, 1)
+    FIELD(IER, PTIME, 7, 1)
+REG32(FCR, 0x08)
+    FIELD(FCR, FIFOE, 0, 1)
+    FIELD(FCR, RFIFOR, 1, 1)
+    FIELD(FCR, XFIFOR, 2, 1)
+    FIELD(FCR, DMAM, 3, 1)
+    FIELD(FCR, TET, 4, 2)
+    FIELD(FCR, RT, 6, 2)
+REG32(IIR, 0x08)
+    FIELD(IIR, IID, 0, 4)
+    FIELD(IIR, FIFOSE, 6, 2)
+REG32(LCR, 0x0c)
+    FIELD(LCR, DLS, 0, 2)
+    FIELD(LCR, STOP, 2, 1)
+    FIELD(LCR, PEN, 3, 1)
+    FIELD(LCR, EPS, 4, 1)
+    FIELD(LCR, SP, 5, 1)
+    FIELD(LCR, BC, 6, 1)
+    FIELD(LCR, DLAB, 7, 1)
+REG32(MCR, 0x10)
+    FIELD(MCR, DTR, 0, 1)
+    FIELD(MCR, RTS, 1, 1)
+    FIELD(MCR, OUT1, 2, 1)
+    FIELD(MCR, OUT2, 3, 1)
+    FIELD(MCR, LOOPBACK, 4, 1)
+    FIELD(MCR, AFCE, 5, 1)
+    FIELD(MCR, SIRE, 6, 1)
+REG32(LSR, 0x14)
+    FIELD(LSR, DR, 0, 1)
+    FIELD(LSR, OE, 1, 1)
+    FIELD(LSR, PE, 2, 1)
+    FIELD(LSR, FE, 3, 1)
+    FIELD(LSR, BI, 4, 1)
+    FIELD(LSR, THRE, 5, 1)
+    FIELD(LSR, TEMT, 6, 1)
+    FIELD(LSR, RFE, 7, 1)
+    FIELD(LSR, ADDR_RSVD, 8, 1)
+REG32(MSR, 0x18)
+    FIELD(MSR, DCTS, 0, 1)
+    FIELD(MSR, DDSR, 1, 1)
+    FIELD(MSR, TERI, 2, 1)
+    FIELD(MSR, DDCD, 3, 1)
+    FIELD(MSR, CTS, 4, 1)
+    FIELD(MSR, DSR, 5, 1)
+    FIELD(MSR, RI, 6, 1)
+    FIELD(MSR, DCD, 7, 1)
+REG32(RFW, 0x78)
+    FIELD(RFW, RFWD, 0, 8)
+    FIELD(RFW, RFPE, 8, 1)
+    FIELD(RFW, RFFE, 9, 1)
+REG32(USR, 0x7c)
+    FIELD(USR, BUSY, 0, 1)
+    FIELD(USR, TFNF, 1, 1)
+    FIELD(USR, TFE, 2, 1)
+    FIELD(USR, RFNE, 3, 1)
+    FIELD(USR, RFF, 4, 1)
+REG32(TFL, 0x80)
+    FIELD(TFL, TFL, 0, 5)
+REG32(RFL, 0x84)
+    FIELD(RFL, RFL, 0, 5)
+REG32(SRR, 0x88)
+    FIELD(SRR, UR, 0, 1)
+    FIELD(SRR, RFR, 1, 1)
+    FIELD(SRR, XFR, 2, 1)
+REG32(SRTS, 0x8c)
+    FIELD(SRTS, SRTS, 0, 1)
+REG32(SBCR, 0x90)
+    FIELD(SBCR, SBCB, 0, 1)
+REG32(SDMAM, 0x94)
+    FIELD(SDMAM, SDMAM, 0, 1)
+REG32(SFE, 0x98)
+    FIELD(SFE, SFE, 0, 1)
+REG32(SRT, 0x9c)
+    FIELD(SRT, SRT, 0, 2)
+REG32(STET, 0xa0)
+    FIELD(STET, STET, 0, 2)
+REG32(HTX, 0xa4)
+    FIELD(HTX, HTX, 0, 1)
+REG32(TCR, 0xac)
+    FIELD(TCR, RS485_EN, 0, 1)
+    FIELD(TCR, RE_POL, 1, 1)
+    FIELD(TCR, DE_POL, 2, 1)
+    FIELD(TCR, XFER_MODE, 3, 2)
+REG32(SCR, 0x1c)
+REG32(DLF, 0xc0)
+REG32(CPR, 0xf4)
+REG32(UCV, 0xf8)
+REG32(CTR, 0xfc)
+
+/* peripheral ID 0x44570110 ("DW\x01\x10").  Read-only. */
+#define K230_UART_CTR_VALUE  0x44570110u
+#define K230_UART_16550_COMPATIBLE    0
+#define K230_UART_FIFO_DEPTH          32
+
+/* CPR */
+#define K230_UART_APB_DATA_WIDTH      2      /* CPR[1:0]  - 32-bit APB       */
+#define K230_UART_AFCE_MODE           0      /* CPR[4]    - not implemented */
+#define K230_UART_THRE_MODE           1      /* CPR[5]    - implemented     */
+#define K230_UART_SIR_MODE            0      /* CPR[6]    - not implemented */
+#define K230_UART_SIR_LP_MODE         0      /* CPR[7]    - not implemented */
+#define K230_UART_ADDITIONAL_FEATURES 1      /* CPR[8]    - UCV/CTR present */
+#define K230_UART_FIFO_ACCESS         0      /* CPR[9]    - not implemented */
+#define K230_UART_FIFO_STAT           1      /* CPR[10]   - TFL/RFL present */
+#define K230_UART_SHADOW              1      /* CPR[11]   - shadow regs     */
+#define K230_UART_ADD_ENCODED_PARAMS  1      /* CPR[12]   - CPR present     */
+#define K230_UART_DMA_EXTRA           0      /* CPR[13]   - not implemented */
+#define K230_UART_FIFO_MODE           0x2    /* CPR[23:16] - 32-byte FIFO   */
+
+#define TYPE_K230_UART "k230-uart"
+OBJECT_DECLARE_SIMPLE_TYPE(K230UartState, K230_UART)
+struct K230UartState {
+    SysBusDevice parent_obj;
+    MemoryRegion mmio;
+
+    /* Standard 16550 registers */
+    uint8_t  dll;       /* Divisor Latch Low,               offset 0x00 */
+    uint8_t  ier;       /* Interrupt Enable,                offset 0x04 */
+    uint8_t  dlh;       /* Divisor Latch High,              offset 0x04 */
+    uint8_t  fcr;       /* FIFO Control,                    offset 0x08 */
+    uint8_t  iir;       /* Interrupt Identification,        offset 0x08 */
+    uint8_t  lcr;       /* Line Control,                    offset 0x0c */
+    uint8_t  mcr;       /* Modem Control,                   offset 0x10 */
+    uint8_t  lsr;       /* Line Status,                     offset 0x14 */
+    uint8_t  msr;       /* Modem Status,                    offset 0x18 */
+    uint8_t  scr;       /* Scratchpad,                      offset 0x1c */
+
+    /* DesignWare-specific registers */
+    uint8_t  usr;       /* UART Status,                     offset 0x7c */
+    uint8_t  htx;       /* Halt TX,                         offset 0xa4 */
+
+    /* Internal interrupt state. */
+    uint8_t  thr_ipending;       /* THR empty (IID=0x2) pending     */
+    uint8_t  timeout_ipending;   /* RX FIFO timeout (IID=0xc)       */
+    uint8_t  busy_ipending;      /* busy detect (IID=0x7) pending   */
+
+    /* FIFO */
+    uint16_t rx_fifo[K230_UART_FIFO_DEPTH];
+    uint32_t rx_head, rx_tail, rx_count;
+    uint16_t tx_fifo[K230_UART_FIFO_DEPTH];
+    uint32_t tx_head, tx_tail, tx_count;
+
+    uint64_t char_transmit_time;
+
+    CharFrontend chr;
+    qemu_irq    irq;
+    QEMUTimer rx_timeout;
+};
+
+#endif

-- 
2.48.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH RESEND v2 2/3] hw/riscv: k230: connect DW 8250 UART
  2026-07-25  3:52 [PATCH RESEND v2 0/3] riscv: Add K230 DW 8250-compatible UART WX Chen
  2026-07-25  3:52 ` [PATCH RESEND v2 1/3] hw/char: add " WX Chen
@ 2026-07-25  3:52 ` WX Chen
  2026-08-05 20:02   ` Daniel Henrique Barboza
  2026-07-25  3:52 ` [PATCH RESEND v2 3/3] tests/qtest: add K230 UART test WX Chen
  2 siblings, 1 reply; 9+ messages in thread
From: WX Chen @ 2026-07-25  3:52 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Marc-André Lureau, Chao Liu, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
	qemu-riscv, Fabiano Rosas, Laurent Vivier, WX Chen

Replace the generic serial-mm device with the K230 DW 8250-compatible
UART model added in the previous patch.

Signed-off-by: WX Chen <wxchen0913@gmail.com>
---
 hw/riscv/k230.c         | 33 ++++++++++++++++++++++-----------
 include/hw/riscv/k230.h |  6 ++++--
 2 files changed, 26 insertions(+), 13 deletions(-)

diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
index 502281c52cff1dce6febb7487dfdefefe6363e9c..d49fa23448f38dfed45a570eb5bece64519bfe27 100644
--- a/hw/riscv/k230.c
+++ b/hw/riscv/k230.c
@@ -29,7 +29,6 @@
 #include "hw/riscv/machines-qom.h"
 #include "hw/intc/riscv_aclint.h"
 #include "hw/intc/sifive_plic.h"
-#include "hw/char/serial-mm.h"
 #include "hw/misc/unimp.h"
 
 /* Align K230_SDK k230_canmv_defconfig */
@@ -111,6 +110,11 @@ static void k230_soc_init(Object *obj)
     object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT);
     object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT);
 
+    for (int i = 0; i < K230_UART_COUNT; i++) {
+        g_autofree char *name = g_strdup_printf("k230-uart%d", i);
+        object_initialize_child(obj, name, &s->uart[i], TYPE_K230_UART);
+    }
+
     qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
     qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
     qdev_prop_set_uint64(DEVICE(cpu0), "resetvec",
@@ -136,19 +140,26 @@ static DeviceState *k230_create_plic(int base_hartid, int hartid_count)
                               memmap[K230_DEV_PLIC].size);
 }
 
-static void k230_create_uart(MemoryRegion *sys_mem, DeviceState *plic,
-                             int index)
+static void k230_create_uart(K230SoCState *s, DeviceState *plic, int index)
 {
     int uart_dev = K230_DEV_UART0 + index;
-    g_autofree char *name = g_strdup_printf("uart%d", index);
+    g_autofree char *unimpl_name = g_strdup_printf("uart%d", index);
+    DeviceState *dev = DEVICE(&s->uart[index]);
 
-    /* Cover the non-16550 part of the SDK's 0x1000 UART window. */
-    create_unimplemented_device(name, memmap[uart_dev].base,
-                                memmap[uart_dev].size);
+    qdev_prop_set_chr(dev, "chardev", serial_hd(index));
 
-    serial_mm_init(sys_mem, memmap[uart_dev].base, 2,
-                   qdev_get_gpio_in(plic, K230_UART0_IRQ + index),
-                   399193, serial_hd(index), DEVICE_LITTLE_ENDIAN);
+    if (!sysbus_realize(SYS_BUS_DEVICE(dev), &error_fatal)) {
+        return;
+    }
+
+    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, memmap[uart_dev].base);
+    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
+                       qdev_get_gpio_in(plic, K230_UART0_IRQ + index));
+
+    /* Cover the non-16550 part of the SDK's 0x1000 UART window. */
+    create_unimplemented_device(unimpl_name,
+                                memmap[uart_dev].base + 0x100,
+                                memmap[uart_dev].size - 0x100);
 }
 
 static void k230_soc_realize(DeviceState *dev, Error **errp)
@@ -188,7 +199,7 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
 
     /* UART */
     for (int i = 0; i < K230_UART_COUNT; i++) {
-        k230_create_uart(sys_mem, DEVICE(s->c908_plic), i);
+        k230_create_uart(s, DEVICE(s->c908_plic), i);
     }
 
     /* Watchdog */
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
index 592e1c26bf8a8f8a1c66653ff9b568fbfe98a3ea..fed0357c2b9e1f5254cb157e0cd4d75d4946d1a1 100644
--- a/include/hw/riscv/k230.h
+++ b/include/hw/riscv/k230.h
@@ -17,10 +17,13 @@
 
 #include "hw/core/boards.h"
 #include "hw/riscv/riscv_hart.h"
+#include "hw/char/k230_uart.h"
 #include "hw/watchdog/k230_wdt.h"
 
 #define C908_CPU_HARTID   (0)
 
+#define K230_UART_COUNT 5
+
 #define TYPE_RISCV_K230_SOC "riscv.k230.soc"
 #define RISCV_K230_SOC(obj) \
     OBJECT_CHECK(K230SoCState, (obj), TYPE_RISCV_K230_SOC)
@@ -32,6 +35,7 @@ typedef struct K230SoCState {
     /*< public >*/
     RISCVHartArrayState c908_cpu; /* Small core */
 
+    K230UartState uart[K230_UART_COUNT];
     K230WdtState wdt[2];
     MemoryRegion sram;
     MemoryRegion bootrom;
@@ -131,8 +135,6 @@ enum {
     K230_WDT1_IRQ   = 108,
 };
 
-#define K230_UART_COUNT 5
-
 /*
  * Integrates with the interrupt controller (PLIC),
  * which can process 208 interrupt external sources

-- 
2.48.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH RESEND v2 3/3] tests/qtest: add K230 UART test
  2026-07-25  3:52 [PATCH RESEND v2 0/3] riscv: Add K230 DW 8250-compatible UART WX Chen
  2026-07-25  3:52 ` [PATCH RESEND v2 1/3] hw/char: add " WX Chen
  2026-07-25  3:52 ` [PATCH RESEND v2 2/3] hw/riscv: k230: connect DW 8250 UART WX Chen
@ 2026-07-25  3:52 ` WX Chen
  2026-08-05 20:10   ` Daniel Henrique Barboza
  2 siblings, 1 reply; 9+ messages in thread
From: WX Chen @ 2026-07-25  3:52 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Marc-André Lureau, Chao Liu, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
	qemu-riscv, Fabiano Rosas, Laurent Vivier, WX Chen

Add 8 test cases covering the main register paths and driver usage
scenarios: device probe, init & baud, TX/RX datapath, THRE interrupt,
RX interrupts (timeout & trigger level), error interrupts & IIR
priority, USR & busy detect, and advanced features (shadow registers,
HTX, SRR, PTIME/TET).

All tests pass.

Signed-off-by: WX Chen <wxchen0913@gmail.com>
---
 tests/qtest/k230-uart-test.c | 514 +++++++++++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build      |   2 +-
 2 files changed, 515 insertions(+), 1 deletion(-)

diff --git a/tests/qtest/k230-uart-test.c b/tests/qtest/k230-uart-test.c
new file mode 100644
index 0000000000000000000000000000000000000000..f85c690ebb57abb5b7f753f36c06c0900c3cfbb9
--- /dev/null
+++ b/tests/qtest/k230-uart-test.c
@@ -0,0 +1,514 @@
+/*
+ * QTest for the K230 UART — functional-path coverage.
+ *
+ * Tests are organised around driver usage scenarios rather than
+ * enumerating every register in isolation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include <string.h>
+
+#define UART_BASE  0x91400000
+#define R(off)     (UART_BASE + (off))
+
+/* register offsets */
+#define THR    0x00
+#define IER    0x04
+#define IIR    0x08
+#define LCR    0x0c
+#define MCR    0x10
+#define LSR    0x14
+#define SCR    0x1c
+#define USR    0x7c
+#define TFL    0x80
+#define RFL    0x84
+#define SRR    0x88
+#define SRTS   0x8c
+#define SBCR   0x90
+#define SDMAM  0x94
+#define SFE    0x98
+#define SRT    0x9c
+#define STET   0xa0
+#define HTX    0xa4
+#define CPR    0xf4
+#define CTR    0xfc
+
+/* bit fields */
+#define LCR_DLAB     0x80
+#define LCR_BC       0x40
+#define LCR_8N1      0x03
+
+#define LSR_DR       0x01
+#define LSR_OE       0x02
+#define LSR_THRE     0x20
+#define LSR_TEMT     0x40
+#define LSR_RESET    0x60
+
+#define IIR_IID      0x0f
+#define IIR_NONE     0x01
+#define IIR_THR      0x02
+#define IIR_RX       0x04
+#define IIR_LINE     0x06
+#define IIR_BUSY     0x07
+#define IIR_TO       0x0c
+#define IIR_FF       0xc0
+
+#define IER_RX       0x01
+#define IER_TX       0x02
+#define IER_LS       0x04
+#define IER_COLR     0x10
+#define IER_PTIME    0x80
+
+#define MCR_LB       0x10
+#define MCR_RTS      0x02
+
+#define FCR_FE       0x01
+#define FCR_RR       0x02
+#define FCR_XR       0x04
+#define FCR_TET_H    (3 << 4)
+#define FCR_RT_Q     (1 << 6)
+#define FCR_RT_F     (3 << 6)
+
+#define USR_BUSY     0x01
+#define USR_RESET    0x06
+
+#define SRR_UR       0x01
+#define SRR_RFR      0x02
+
+/* helpers */
+static uint32_t rd(QTestState *qts, uint32_t o)
+{
+    return qtest_readl(qts, R(o));
+}
+static uint32_t iid(QTestState *qts)
+{
+    return rd(qts, IIR) & IIR_IID;
+}
+
+static void poll_lsr(QTestState *qts, uint32_t m)
+{
+    int i;
+
+    for (i = 0; i < 1000; i++) {
+        if (rd(qts, LSR) & m) {
+            return;
+        }
+        g_usleep(1000);
+    }
+    g_assert_not_reached();
+}
+
+static void s1(QTestState *qts, int fd, char c)
+{
+    g_assert_cmpint(send(fd, &c, 1, 0), ==, 1);
+    poll_lsr(qts, LSR_DR);
+}
+
+static void sn(QTestState *qts, int fd, const char *d, int n)
+{
+    g_assert_cmpint(send(fd, d, n, 0), ==, n);
+    poll_lsr(qts, LSR_DR);
+}
+
+static void oe_nf(QTestState *qts, int fd)
+{
+    int i;
+
+    s1(qts, fd, 'A');
+    {
+        char c = 'B';
+        g_assert_cmpint(send(fd, &c, 1, 0), ==, 1);
+    }
+    for (i = 0; i < 200; i++) {
+        rd(qts, SCR);
+        g_usleep(1000);
+    }
+}
+
+/* 1. device probe */
+static void test_device_probe(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+    uint32_t cpr = rd(qts, CPR);
+
+    g_assert_cmphex(cpr & 0x3, ==, 0x2);
+    g_assert_cmphex(cpr & (1 << 5), ==, (1 << 5));
+    g_assert_cmphex(cpr & (1 << 8), ==, (1 << 8));
+    g_assert_cmphex((cpr >> 16) & 0xff, ==, 0x2);
+    g_assert_cmphex(cpr & (1 << 4), ==, 0);
+    g_assert_cmphex(rd(qts, CTR), ==, 0x44570110);
+    g_assert_cmphex(rd(qts, LSR), ==, LSR_RESET);
+    g_assert_cmphex(rd(qts, USR), ==, USR_RESET);
+    g_assert_cmphex(iid(qts), ==, IIR_NONE);
+    g_assert_cmphex(rd(qts, IIR) & IIR_FF, ==, 0);
+    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
+
+    qtest_writel(qts, R(IIR), FCR_FE);
+    g_assert_cmphex(rd(qts, IIR) & IIR_FF, ==, IIR_FF);
+    qtest_quit(qts);
+}
+
+/* 2. init & baud */
+static void test_init_and_baud(void)
+{
+    int fd;
+    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
+
+    qtest_writel(qts, R(LCR), LCR_DLAB);
+    qtest_writel(qts, R(THR), 0x55);
+    g_assert_cmphex(rd(qts, THR), ==, 0x55);
+    qtest_writel(qts, R(LCR), LCR_8N1);
+    g_assert_cmphex(rd(qts, THR), ==, 0x00);
+
+    qtest_writel(qts, R(IIR), FCR_FE | FCR_RT_F);
+    qtest_writel(qts, R(IER), IER_RX);
+
+    /* divisor=1 -> timeout=12800ns */
+    qtest_writel(qts, R(LCR), LCR_8N1 | LCR_DLAB);
+    qtest_writel(qts, R(THR), 1); qtest_writel(qts, R(IER), 0);
+    qtest_writel(qts, R(LCR), LCR_8N1);
+    s1(qts, fd, 'X');
+    qtest_clock_step(qts, 13000);
+    g_assert_cmphex(iid(qts), ==, IIR_TO);
+
+    /* divisor=100 -> timeout=1.28e6ns; 13000ns too short */
+    rd(qts, THR);
+    qtest_writel(qts, R(LCR), LCR_8N1 | LCR_DLAB);
+    qtest_writel(qts, R(THR), 100); qtest_writel(qts, R(IER), 0);
+    qtest_writel(qts, R(LCR), LCR_8N1);
+    s1(qts, fd, 'Y');
+    qtest_clock_step(qts, 13000);
+    g_assert_cmphex(iid(qts), !=, IIR_TO);
+    qtest_clock_step(qts, 1300000);
+    g_assert_cmphex(iid(qts), ==, IIR_TO);
+
+    close(fd); qtest_quit(qts);
+}
+
+/* 3. TX / RX datapath */
+static void test_tx_rx_datapath(void)
+{
+    int fd;
+    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
+
+    qtest_writel(qts, R(LCR), LCR_8N1);
+    g_assert_cmphex(rd(qts, LSR) & (LSR_THRE | LSR_TEMT),
+                    ==, LSR_THRE | LSR_TEMT);
+    qtest_writel(qts, R(THR), 'A');
+    g_assert_cmphex(rd(qts, LSR) & (LSR_THRE | LSR_TEMT),
+                    ==, LSR_THRE | LSR_TEMT);
+
+    /* external RX (FIFO mode) */
+    qtest_writel(qts, R(IIR), FCR_FE);
+    qtest_writel(qts, R(IER), IER_RX);
+    sn(qts, fd, "K230", 4);
+    for (int i = 0; i < 4; i++) {
+        if (i < 3) {
+            g_assert_cmphex(iid(qts), ==, IIR_RX);
+        }
+        g_assert_cmphex(rd(qts, THR), ==, "K230"[i]);
+    }
+    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
+
+    /* loopback */
+    qtest_writel(qts, R(MCR), MCR_LB);
+    qtest_writel(qts, R(THR), 'L');
+    g_assert_cmphex(rd(qts, THR), ==, 'L');
+    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
+
+    /* no loopback */
+    qtest_writel(qts, R(MCR), 0);
+    qtest_writel(qts, R(THR), 'Z');
+    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
+
+    /* TFL / RFL */
+    qtest_writel(qts, R(MCR), MCR_LB);
+    qtest_writel(qts, R(IIR), FCR_FE | FCR_RR | FCR_XR);
+    qtest_writel(qts, R(THR), 'X'); qtest_writel(qts, R(THR), 'Y');
+    g_assert_cmphex(rd(qts, TFL), ==, 0);
+    g_assert_cmphex(rd(qts, RFL), ==, 2);
+
+    /* non-FIFO mode */
+    qtest_writel(qts, R(MCR), 0);
+    qtest_writel(qts, R(IIR), 0);
+    s1(qts, fd, 'N');
+    g_assert_cmphex(rd(qts, THR), ==, 'N');
+    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
+
+    /* non-FIFO overrun */
+    int i;
+    s1(qts, fd, 'n');
+    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, LSR_DR);
+    {
+        char c = 'o';
+        send(fd, &c, 1, 0);
+    }
+    for (i = 0; i < 200; i++) {
+        rd(qts, SCR);
+        g_usleep(1000);
+    }
+    g_assert_cmphex(rd(qts, LSR) & LSR_OE, ==, LSR_OE);
+
+    close(fd); qtest_quit(qts);
+}
+
+/* 4. THRE interrupt */
+static void test_thre_interrupt(void)
+{
+    QTestState *qts = qtest_init("-machine k230 "
+                                 "-chardev null,id=c0 -serial chardev:c0");
+    qtest_writel(qts, R(LCR), LCR_8N1);
+
+    qtest_writel(qts, R(THR), 'A');
+    g_assert_cmphex(iid(qts), ==, IIR_NONE);
+
+    qtest_writel(qts, R(IER), IER_TX);
+    g_assert_cmphex(iid(qts), ==, IIR_THR);
+
+    qtest_writel(qts, R(THR), 'B');
+    g_assert_cmphex(iid(qts), ==, IIR_THR);
+
+    g_assert_cmphex(iid(qts), ==, IIR_NONE);
+    qtest_quit(qts);
+}
+
+/* 5. RX interrupts: timeout & trigger level */
+static void test_rx_interrupts(void)
+{
+    int fd;
+    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
+
+    qtest_writel(qts, R(LCR), LCR_8N1);
+    qtest_writel(qts, R(IIR), FCR_FE | FCR_RT_F);
+    qtest_writel(qts, R(IER), IER_RX);
+
+    /* basic timeout */
+    s1(qts, fd, 'T');
+    qtest_clock_step(qts, 400000);
+    g_assert_cmphex(iid(qts), ==, IIR_TO);
+    g_assert_cmphex(rd(qts, THR), ==, 'T');
+    g_assert_cmphex(iid(qts), !=, IIR_TO);
+
+    /* timeout reset by new byte */
+    s1(qts, fd, 'A'); qtest_clock_step(qts, 200000);
+    s1(qts, fd, 'B');
+    qtest_clock_step(qts, 200000);
+    g_assert_cmphex(iid(qts), ==, IIR_NONE);
+    qtest_clock_step(qts, 200000);
+    g_assert_cmphex(iid(qts), ==, IIR_TO);
+    rd(qts, THR);
+    rd(qts, THR);
+
+    /* timeout rearmed by partial drain */
+    s1(qts, fd, 'X');
+    s1(qts, fd, 'Y');
+    qtest_clock_step(qts, 400000);
+    g_assert_cmphex(iid(qts), ==, IIR_TO);
+    g_assert_cmphex(rd(qts, THR), ==, 'X');
+    qtest_clock_step(qts, 200000);
+    g_assert_cmphex(iid(qts), !=, IIR_TO);
+    qtest_clock_step(qts, 250000);
+    g_assert_cmphex(iid(qts), ==, IIR_TO);
+    g_assert_cmphex(rd(qts, THR), ==, 'Y');
+
+    /* RX trigger level: RT=Q -> trigger at 8 bytes */
+    qtest_writel(qts, R(IIR), FCR_FE | FCR_RT_Q);
+    sn(qts, fd, "ABCDEFG", 7);
+    g_usleep(20000);
+    g_assert_cmphex(iid(qts), ==, IIR_NONE);
+    qtest_clock_step(qts, 400000);
+    g_assert_cmphex(iid(qts), ==, IIR_TO);
+    for (int i = 0; i < 7; i++) {
+        rd(qts, THR);
+    }
+
+    sn(qts, fd, "12345678", 8);
+    for (int i = 0; i < 1000; i++) {
+        if (iid(qts) == IIR_RX) {
+            break;
+        }
+        g_usleep(1000);
+    }
+    g_assert_cmphex(iid(qts), ==, IIR_RX);
+
+    close(fd); qtest_quit(qts);
+}
+
+/* 6. error interrupts & IIR priority */
+static void test_error_interrupts(void)
+{
+    int fd;
+    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
+
+    qtest_writel(qts, R(LCR), LCR_8N1);
+    qtest_writel(qts, R(IER), IER_LS);
+
+    /* OE -> IIR=0x6; LSR clears; ELCOLR=0: RBR clears; ELCOLR=1: RBR keeps */
+    oe_nf(qts, fd);
+    g_assert_cmphex(iid(qts), ==, IIR_LINE);
+    rd(qts, LSR);
+    g_assert_cmphex(iid(qts), !=, IIR_LINE);
+
+    oe_nf(qts, fd);
+    g_assert_cmphex(iid(qts), ==, IIR_LINE);
+    rd(qts, THR);
+    g_assert_cmphex(iid(qts), !=, IIR_LINE);
+
+    qtest_writel(qts, R(IER), IER_LS | IER_COLR);
+    oe_nf(qts, fd);
+    g_assert_cmphex(iid(qts), ==, IIR_LINE);
+    rd(qts, THR);
+    g_assert_cmphex(iid(qts), ==, IIR_LINE);
+    rd(qts, LSR);
+    g_assert_cmphex(iid(qts), !=, IIR_LINE);
+
+    /* IIR priority: RX > TX (loopback) */
+    qtest_writel(qts, R(IIR), FCR_FE);
+    qtest_writel(qts, R(MCR), MCR_LB);
+    qtest_writel(qts, R(IER), IER_TX | IER_RX);
+    qtest_writel(qts, R(THR), 'P');
+    g_assert_cmphex(iid(qts), ==, IIR_RX);
+    g_assert_cmphex(rd(qts, THR), ==, 'P');
+    g_assert_cmphex(iid(qts), ==, IIR_THR);
+    iid(qts);
+    g_assert_cmphex(iid(qts), ==, IIR_NONE);
+
+    /* OE in FIFO mode via loopback */
+    qtest_writel(qts, R(IER), IER_LS);
+    for (int i = 0; i < 32; i++) {
+        qtest_writel(qts, R(THR), 'a');
+    }
+    g_assert_cmphex(rd(qts, LSR) & LSR_OE, ==, 0);
+    qtest_writel(qts, R(THR), 'z');
+    g_assert_cmphex(iid(qts), ==, IIR_LINE);
+    g_assert_cmphex(rd(qts, LSR) & LSR_OE, ==, LSR_OE);
+
+    close(fd); qtest_quit(qts);
+}
+
+/* 7. USR & busy detect */
+static void test_busy_detect(void)
+{
+    int fd;
+    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
+
+    qtest_writel(qts, R(LCR), LCR_8N1);
+    qtest_writel(qts, R(IIR), FCR_FE);
+
+    g_assert_cmphex(rd(qts, USR) & 0x1e, ==, 0x06);
+
+    qtest_writel(qts, R(MCR), MCR_LB);
+    qtest_writel(qts, R(THR), 'A');
+    g_assert_cmphex(rd(qts, USR) & 0x08, ==, 0x08);
+    rd(qts, THR);
+    g_assert_cmphex(rd(qts, USR) & 0x08, ==, 0);
+    qtest_writel(qts, R(MCR), 0);
+
+    /* BUSY via RX & loopback */
+    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
+    s1(qts, fd, 'Z');
+    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, USR_BUSY);
+    rd(qts, THR);
+    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
+
+    qtest_writel(qts, R(MCR), MCR_LB);
+    qtest_writel(qts, R(THR), 'L');
+    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, USR_BUSY);
+    rd(qts, THR);
+    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
+
+    /* busy-detect: LCR write rejected while BUSY=1 */
+    qtest_writel(qts, R(MCR), 0);
+    qtest_writel(qts, R(IIR), FCR_FE | FCR_RR | FCR_XR);
+    s1(qts, fd, 'B');
+    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, USR_BUSY);
+    qtest_writel(qts, R(LCR), LCR_8N1 | LCR_DLAB);
+    g_assert_cmphex(rd(qts, LCR), ==, LCR_8N1);
+    g_assert_cmphex(iid(qts), ==, IIR_BUSY);
+    rd(qts, USR);
+    g_assert_cmphex(iid(qts), ==, IIR_NONE);
+    qtest_writel(qts, R(IIR), FCR_FE | FCR_RR);
+    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
+    qtest_writel(qts, R(LCR), LCR_8N1 | LCR_DLAB);
+    g_assert_cmphex(rd(qts, LCR), ==, LCR_8N1 | LCR_DLAB);
+
+    close(fd); qtest_quit(qts);
+}
+
+/* 8. advanced features */
+static void test_advanced_features(void)
+{
+    QTestState *qts = qtest_init("-machine k230 "
+                                 "-chardev null,id=c0 -serial chardev:c0");
+
+    /* shadow: SRTS<->MCR.RTS, SBCR<->LCR.BC, SDMAM, SFE, SRT, STET, HTX */
+    qtest_writel(qts, R(SRTS), 1);
+    g_assert_cmphex(rd(qts, MCR) & MCR_RTS, ==, MCR_RTS);
+    qtest_writel(qts, R(MCR), 0); g_assert_cmphex(rd(qts, SRTS), ==, 0);
+    qtest_writel(qts, R(SBCR), 1);
+    g_assert_cmphex(rd(qts, LCR) & LCR_BC, ==, LCR_BC);
+    qtest_writel(qts, R(LCR), 0); g_assert_cmphex(rd(qts, SBCR), ==, 0);
+    qtest_writel(qts, R(SDMAM), 1); g_assert_cmphex(rd(qts, SDMAM), ==, 1);
+    qtest_writel(qts, R(SRT), 0x2); g_assert_cmphex(rd(qts, SRT), ==, 0x2);
+    qtest_writel(qts, R(STET), 0x3); g_assert_cmphex(rd(qts, STET), ==, 0x3);
+    qtest_writel(qts, R(SFE), 1); g_assert_cmphex(rd(qts, SFE), ==, 1);
+    g_assert_cmphex(rd(qts, IIR) & IIR_FF, ==, IIR_FF);
+    qtest_writel(qts, R(HTX), 1); g_assert_cmphex(rd(qts, HTX), ==, 1);
+    qtest_writel(qts, R(HTX), 0); g_assert_cmphex(rd(qts, HTX), ==, 0);
+
+    /* HTX halt TX */
+    qtest_writel(qts, R(LCR), LCR_8N1);
+    qtest_writel(qts, R(IIR), FCR_FE);
+    qtest_writel(qts, R(MCR), MCR_LB);
+
+    qtest_writel(qts, R(HTX), 1);
+    qtest_writel(qts, R(THR), 'H');
+    g_assert_cmphex(rd(qts, TFL), ==, 1);
+    g_assert_cmphex(rd(qts, RFL), ==, 0);
+
+    qtest_writel(qts, R(HTX), 0);
+    g_assert_cmphex(rd(qts, TFL), ==, 0);
+    g_assert_cmphex(rd(qts, RFL), ==, 1);
+    g_assert_cmphex(rd(qts, THR), ==, 'H');
+
+    /* SRR: RFR & UR */
+    qtest_writel(qts, R(THR), 'Z');
+    g_assert_cmphex(rd(qts, RFL), ==, 1);
+    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, LSR_DR);
+    qtest_writel(qts, R(SRR), SRR_RFR);
+    g_assert_cmphex(rd(qts, RFL), ==, 0);
+    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
+    g_assert_cmphex(rd(qts, SRR), ==, 0);
+    qtest_writel(qts, R(THR), 'Y');
+    qtest_writel(qts, R(SRR), SRR_UR);
+    g_assert_cmphex(rd(qts, LSR), ==, LSR_RESET);
+    g_assert_cmphex(iid(qts), ==, IIR_NONE);
+    g_assert_cmphex(rd(qts, RFL), ==, 0);
+
+    /* PTIME + TET programmable THRE */
+    qtest_writel(qts, R(LCR), LCR_8N1);
+    qtest_writel(qts, R(IIR), FCR_FE | FCR_TET_H);
+    qtest_writel(qts, R(IER), IER_TX | IER_PTIME);
+    qtest_writel(qts, R(THR), 'A');
+    g_assert_cmphex(rd(qts, LSR) & LSR_THRE, ==, LSR_THRE);
+    g_assert_cmphex(iid(qts), ==, IIR_THR);
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char *argv[])
+{
+    g_test_init(&argc, &argv, NULL);
+    qtest_add_func("/k230-uart/device_probe",       test_device_probe);
+    qtest_add_func("/k230-uart/init_and_baud",      test_init_and_baud);
+    qtest_add_func("/k230-uart/tx_rx_datapath",     test_tx_rx_datapath);
+    qtest_add_func("/k230-uart/thre_interrupt",     test_thre_interrupt);
+    qtest_add_func("/k230-uart/rx_interrupts",      test_rx_interrupts);
+    qtest_add_func("/k230-uart/error_interrupts",   test_error_interrupts);
+    qtest_add_func("/k230-uart/busy_detect",        test_busy_detect);
+    qtest_add_func("/k230-uart/advanced_features",  test_advanced_features);
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 822e0bd286970339fe28127649feb131ce8a81b2..93246d9bf77206e9cebb9d8ade5801f4541d3b8d 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -294,7 +294,7 @@ qtests_riscv64 = ['riscv-csr-test'] + \
   (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
    config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
    ['iommu-riscv-test'] : []) + \
-  (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
+  (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test', 'k230-uart-test'] : [])
 
 qtests_hexagon = ['boot-serial-test']
 

-- 
2.48.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH RESEND v2 1/3] hw/char: add K230 DW 8250-compatible UART
  2026-07-25  3:52 ` [PATCH RESEND v2 1/3] hw/char: add " WX Chen
@ 2026-08-05 20:01   ` Daniel Henrique Barboza
  2026-08-07  9:49     ` zhenbaii
  0 siblings, 1 reply; 9+ messages in thread
From: Daniel Henrique Barboza @ 2026-08-05 20:01 UTC (permalink / raw)
  To: WX Chen, qemu-devel
  Cc: Paolo Bonzini, Marc-André Lureau, Chao Liu, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Liu Zhiwei, qemu-riscv,
	Fabiano Rosas, Laurent Vivier

Hello,

On 7/25/2026 12:52 AM, WX Chen wrote:
> Implement a K230 SoC DesignWare 8250-compatible UART controller model for
> QEMU, capable of running the Linux 8250_dw driver and providing an
> interactive shell.
> 
> Implemented:
>    - Standard 16550 registers (RBR/THR/DLL, IER/DLH, IIR/FCR, LCR, MCR,
>      LSR, MSR, SCR) and DesignWare-specific registers (USR, TFL, RFL, SRR,
>      SRTS, SBCR, SDMAM, SFE, SRT, STET, HTX, CPR, UCV, CTR)
>    - DLAB switching; 32-byte TX/RX FIFO; synchronous transmit with
>      backpressure handling
>    - Loopback mode; chardev BREAK routed to LSR.BI/FE via CHR_EVENT_BREAK
>    - Four-level prioritized interrupt scheme (ELSI / RX Data / Timeout /
>      THRE), with edge-triggered THRE
>    - RX Character Timeout interrupt (IID=0xc) with 4-char-time timer that
>      tracks the programmed divisor latch
>    - Busy Detect interrupt (IID=0x7): LCR (and its shadow SBCR) writes
>      while USR.BUSY=1 are rejected and raise IID=0x7, cleared by reading
>      USR; USR.BUSY reflects TX-not-empty / RX-data-ready
>    - Shadow registers alias their underlying fields
>    - SRR (UR/RFR/XFR) self-clearing software reset
> 
> Not implemented:
>    - RS485 transceiver control (TCR/DE_EN/RE_EN/DET/TAT)
>    - 9-bit multidrop (LCR_EXT/RAR/TAR)
>    - Fractional baud rate (DLF)
>    - Auto Flow Control (MCR.AFCE)
>    - IrDA SIR mode (MCR.SIRE)
>    - Low-power divisor latch (LPDLL/LPDLH)
>    - FIFO access test mode (FAR/TFR/RFW)
>    - DMA Software Acknowledge (DMASA)
> 
> Signed-off-by: WX Chen <wxchen0913@gmail.com>
> ---

LGTM overall.  Some comments below:



>   hw/char/Kconfig             |   3 +
>   hw/char/k230_uart.c         | 840 ++++++++++++++++++++++++++++++++++++++++++++
>   hw/char/meson.build         |   1 +
>   include/hw/char/k230_uart.h | 180 ++++++++++
>   4 files changed, 1024 insertions(+)
> 
> diff --git a/hw/char/Kconfig b/hw/char/Kconfig
> index 020c0a84bb6e5f11cc7d532a7999ade23505a1ba..bd000f7127e7881dabac191c81b2bcf66f549250 100644
> --- a/hw/char/Kconfig
> +++ b/hw/char/Kconfig
> @@ -91,6 +91,9 @@ config GOLDFISH_TTY
>   config SHAKTI_UART
>       bool
>   
> +config K230_UART
> +    bool
> +
>   config IP_OCTAL_232
>       bool
>       default y
> diff --git a/hw/char/k230_uart.c b/hw/char/k230_uart.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..36562b37463461e039f3096542fd142898dfbae6
> --- /dev/null
> +++ b/hw/char/k230_uart.c
> @@ -0,0 +1,840 @@
> +/*
> + * K230 UART
> + *
> + * Copyright (c) 2026 WX Chen <wxchen0913@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/char/k230_uart.h"
> +#include "hw/core/irq.h"
> +#include "hw/core/qdev-properties-system.h"
> +#include "migration/vmstate.h"
> +#include "qemu/module.h"
> +#include "qemu/timer.h"
> +#include "qemu/host-utils.h"
> +#include "system/memory.h"
> +
> +/* Serial clock from the K230 DT "clock-frequency" property (50 MHz). */
> +#define K230_UART_SCLK_HZ   50000000ull
> +
> +static void k230_uart_xmit(K230UartState *s);
> +static void k230_uart_update_all(K230UartState *s);
> +static uint64_t k230_uart_read_rbr(K230UartState *s);
> +static void k230_uart_push_rx_byte(K230UartState *s, uint8_t ch);
> +static void k230_uart_reset_hold(Object *obj, ResetType type);
> +
> +/* ---- interrupt line ---- */
> +
> +/*
> + * RX FIFO trigger level. When rx_count reaches this,
> + * the RX Data Available interrupt (IID=0x4) fires.
> + */
> +static uint32_t k230_uart_rx_trigger_level(const K230UartState *s)
> +{
> +    if (!FIELD_EX32(s->fcr, FCR, FIFOE)) {
> +        return 1;  /* non-FIFO mode: 1 byte */
> +    }
> +    switch (FIELD_EX32(s->fcr, FCR, RT)) {
> +    case 0:  return 1;
> +    case 1:  return K230_UART_FIFO_DEPTH / 4;
> +    case 2:  return K230_UART_FIFO_DEPTH / 2;
> +    case 3:  return K230_UART_FIFO_DEPTH - 2;
> +    default: return 1;
> +    }
> +}
> +
> +static void k230_uart_update_irq(K230UartState *s)
> +{
> +    bool irq = false;
> +    uint32_t rx_itl = k230_uart_rx_trigger_level(s);
> +
> +    /* RX trigger level > 1 */
> +    if (FIELD_EX32(s->ier, IER, ERBFI) &&
> +        (s->rx_count >= rx_itl || s->timeout_ipending)) {
> +        irq = true;
> +    }
> +    /* Transmit Holding Register Empty Interrupt */
> +    if (FIELD_EX32(s->ier, IER, ETBEI) && s->thr_ipending) {
> +        irq = true;
> +    }
> +    /* Receiver Line Status Interrupt */
> +    if (FIELD_EX32(s->ier, IER, ELSI) &&
> +        (FIELD_EX32(s->lsr, LSR, OE) || FIELD_EX32(s->lsr, LSR, PE) ||
> +         FIELD_EX32(s->lsr, LSR, FE) || FIELD_EX32(s->lsr, LSR, BI))) {
> +        irq = true;

This pattern is repeated at the start of k230_uart_update_iir():


 > +static void k230_uart_update_iir(K230UartState *s)
 > +{
 > +    s->iir = 0;
 > +    /* Indicate whether FIFO is enabled or not */
 > +    if (FIELD_EX32(s->fcr, FCR, FIFOE)) {
 > +        s->iir = FIELD_DP32(s->iir, IIR, FIFOSE, 0x3);
 > +    }
 > +
 > +    if (FIELD_EX32(s->ier, IER, ELSI) &&
 > +        (FIELD_EX32(s->lsr, LSR, OE) || FIELD_EX32(s->lsr, LSR, PE) ||
 > +         FIELD_EX32(s->lsr, LSR, FE) || FIELD_EX32(s->lsr, LSR, BI))) {
 > +        /* Receiver Line Status Interrupt */
 > +        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x6);


I believe a helper like 'static bool k230_uart_receiver_status_int' that
encapsulates it is more idiomatic.


Now, down there:

> +    }
> +    /* Busy detect interrupt (DW-specific, no IER gate). */
> +    if (s->busy_ipending) {
> +        irq = true;
> +    }
> +    qemu_set_irq(s->irq, irq ? 1 : 0);
> +}
> +
> +/* ---- FIFO reset ---- */
> +
> +static void k230_uart_fifo_reset(K230UartState *s, bool rx, bool tx)
> +{
> +    if (rx) {
> +        s->rx_head = s->rx_tail = s->rx_count = 0;
> +        s->lsr = FIELD_DP32(s->lsr, LSR, DR, 0);
> +        s->timeout_ipending = 0;
> +        timer_del(&s->rx_timeout);
> +    }
> +    if (tx) {
> +        s->tx_head = s->tx_tail = s->tx_count = 0;
> +        s->lsr = FIELD_DP32(s->lsr, LSR, THRE, 1);
> +        s->lsr = FIELD_DP32(s->lsr, LSR, TEMT, 1);
> +        s->thr_ipending = 1;
> +    }
> +    k230_uart_update_all(s);
> +}
> +
> +/* ---- transmit ---- */
> +
> +static gboolean k230_uart_chr_can_write(void *do_not_use, GIOCondition cond,
> +                                        void *opaque)
> +{
> +    K230UartState *s = K230_UART(opaque);
> +    if (!s->htx) {
> +        k230_uart_xmit(s);
> +    }
> +    return G_SOURCE_REMOVE;
> +}
> +
> +/* ---- baud / char-transmit time ---- */
> +
> +/*
> + * Recompute the per-character transmit time from the current divisor latch:
> + *
> + *   baud    = sclk / (16 * divisor)
> + *   char_ns = 10 * 16 * divisor * 1e9 / sclk     (10 = 8N1 start+data+stop)
> + *
> + * Drives the RX timeout interrupt (IID=0xc), which fires after 4 char times.
> + * divisor == 0 means "unprogrammed": keep the previous (reset default) value.
> + */
> +static void k230_uart_update_char_time(K230UartState *s)
> +{
> +    uint32_t divisor = ((uint32_t)s->dlh << 8) | s->dll;
> +    if (divisor == 0) {
> +        return;
> +    }
> +    s->char_transmit_time = muldiv64(10ull * 16 * divisor,
> +                                     NANOSECONDS_PER_SECOND,
> +                                     K230_UART_SCLK_HZ);
> +}
> +
> +/* Send all characters in THR or TX-FIFO into char backend */
> +static void k230_uart_xmit(K230UartState *s)
> +{
> +    int ret;
> +
> +    if (s->tx_count == 0 || s->htx) {
> +        return;
> +    }
> +
> +    while (s->tx_count > 0) {
> +        uint8_t ch = (uint8_t)s->tx_fifo[s->tx_tail];
> +
> +        if (FIELD_EX32(s->mcr, MCR, LOOPBACK)) {
> +            /* Go to RX fifo */
> +            k230_uart_push_rx_byte(s, ch);
> +            ret = 1;
> +        } else {
> +            /* Go to char backend */
> +            ret = qemu_chr_fe_write(&s->chr, &ch, 1);
> +        }
> +        if (ret < 0) {
> +            /* Wait for backend to be accessible then call the callback */
> +            qemu_chr_fe_add_watch(&s->chr, G_IO_OUT,
> +                                  k230_uart_chr_can_write, s);> +            break;
> +        }
> +
> +        s->tx_tail = (s->tx_tail + 1) % K230_UART_FIFO_DEPTH;
> +        s->tx_count--;
> +    }
> +
> +    s->lsr = FIELD_DP32(s->lsr, LSR, TEMT, (s->tx_count == 0) ? 1 : 0);
> +    k230_uart_update_all(s);
> +}
> +
> +/* ---- RX timeout ---- */
> +
> +/* Character timeout callback */
> +static void k230_uart_rx_timeout(void *opaque)
> +{
> +    K230UartState *s = K230_UART(opaque);
> +    if (s->rx_count > 0) {
> +        s->timeout_ipending = 1;
> +        k230_uart_update_all(s);
> +    }
> +}
> +
> +/* ---- receive ---- */
> +
> +/* Push a byte to rxfifo(when FIFO_EN is set) or RBR(FIFO_EN not set). */
> +static void k230_uart_push_rx_byte(K230UartState *s, uint8_t ch)
> +{
> +    bool fifo_en = FIELD_EX32(s->fcr, FCR, FIFOE);
> +    uint16_t capacity = fifo_en ? K230_UART_FIFO_DEPTH : 1;
> +
> +    if (s->rx_count < capacity) {
> +        s->rx_fifo[s->rx_head] = ch;
> +        s->rx_head = (s->rx_head + 1) % K230_UART_FIFO_DEPTH;
> +        s->rx_count++;
> +        s->lsr = FIELD_DP32(s->lsr, LSR, DR, 1);
> +        timer_mod(&s->rx_timeout,
> +                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
> +                  + 4 * s->char_transmit_time);
> +    } else {
> +        s->lsr = FIELD_DP32(s->lsr, LSR, OE, 1);
> +    }
> +    k230_uart_update_all(s);
> +}
> +
> +/* ---- derived register recomputation ---- */
> +
> +static void k230_uart_update_thre(K230UartState *s)
> +{
> +    bool should_set = (s->tx_count == 0);
> +#if K230_UART_THRE_MODE


I don't understand the intent of this preprocessor #if K230_UART_THRE_MODE.
AFAIK this is hardcoded to 1 and it's not something we can set during
./configure or anything:

$ git grep K230_UART_THRE_MODE
hw/char/k230_uart.c:#if K230_UART_THRE_MODE
hw/char/k230_uart.c:                   (K230_UART_THRE_MODE                << 5)   |
hw/char/k230_uart.c:#if K230_UART_THRE_MODE
hw/char/k230_uart.c:#if K230_UART_THRE_MODE
hw/char/k230_uart.c:#if K230_UART_THRE_MODE
include/hw/char/k230_uart.h:#define K230_UART_THRE_MODE           1      /* CPR[5]    - implemented     */


Therefore "#if K230_UART_THRE_MODE" will always be true and the code will always
be included.

Similar note for all other preprocessor #if in the code:

-  #if !K230_UART_16550_COMPATIBLE  --> K230_UART_16550_COMPATIBLE is hardcoded 0, thus
this will always be 1.

-  #if K230_UART_FIFO_STAT && K230_UART_FIFO_MODE ->  those are hardcoded to 1 and 2,
so this will always be true as well.


If I have to make a guess I think these macros were being used as easy knobs to test
the emulation, i.e. manually enabling/disabling things in the header file, rebuilding
and see if the behaves as expected.  Which is of course fine, but unless we're
willing to make these knobs available to users (either by making these available during
./configure or create device properties we can set in the command line) I think we
shouldn't use them in the logic since they're all hardwired.


This is more pronounced down there in k230_uart_read().  We have this:

 > +            /* DLL accessible only when not busy. */
 > +            ret = (K230_UART_16550_COMPATIBLE || !k230_uart_is_busy(s))
 > +                  ? s->dll : 0;

K230_UART_16550_COMPATIBLE is hardwired to 0, hence this is equal to:

ret = !k230_uart_is_busy(s) ? s->dll : 0;

I believe we have more instances where we have a hardwired macro being used
as conditionals in the logic.  We should eliminate all of them to make the
logic simpler.

One last thing:


> +    if (FIELD_EX32(s->ier, IER, PTIME)
> +        && FIELD_EX32(s->fcr, FCR, FIFOE)) {
> +        uint8_t tet = FIELD_EX32(s->fcr, FCR, TET);
> +        uint16_t threshold;
> +        switch (tet) {
> +        case 0x0:
> +            threshold = 0;
> +            break;
> +        case 0x1:
> +            threshold = 2;
> +            break;
> +        case 0x2:
> +            threshold = K230_UART_FIFO_DEPTH / 4;
> +            break;
> +        case 0x3:
> +            threshold = K230_UART_FIFO_DEPTH / 2;
> +            break;
> +        default:
> +            threshold = 0;
> +            break;
> +        }
> +        should_set = (s->tx_count <= threshold);
> +    }
> +#endif
> +
> +    uint8_t old_thre = FIELD_EX32(s->lsr, LSR, THRE);
> +    s->lsr = FIELD_DP32(s->lsr, LSR, THRE, should_set ? 1 : 0);
> +
> +    /* Edge trigger */
> +    if (should_set && !old_thre) {
> +        s->thr_ipending = 1;
> +    }
> +}
> +
> +static bool k230_uart_is_busy(const K230UartState *s)
> +{
> +    return !FIELD_EX32(s->lsr, LSR, TEMT) || FIELD_EX32(s->lsr, LSR, DR);
> +}
> +
> +static void k230_uart_update_usr(K230UartState *s)
> +{
> +    bool fifo_en = FIELD_EX32(s->fcr, FCR, FIFOE);
> +    uint16_t capacity = fifo_en ? K230_UART_FIFO_DEPTH : 1;
> +
> +    s->usr = 0;
> +#if !K230_UART_16550_COMPATIBLE
> +    if (k230_uart_is_busy(s)) {
> +        s->usr = FIELD_DP32(s->usr, USR, BUSY, 1);
> +    }
> +#endif
> +#if K230_UART_FIFO_STAT && K230_UART_FIFO_MODE
> +    s->usr = FIELD_DP32(s->usr, USR, TFNF, s->tx_count < capacity ? 1 : 0);
> +    s->usr = FIELD_DP32(s->usr, USR, TFE, s->tx_count == 0 ? 1 : 0);
> +    s->usr = FIELD_DP32(s->usr, USR, RFNE, s->rx_count != 0 ? 1 : 0);
> +    s->usr = FIELD_DP32(s->usr, USR, RFF, s->rx_count == capacity ? 1 : 0);
> +#endif
> +}
> +
> +static void k230_uart_update_iir(K230UartState *s)
> +{
> +    s->iir = 0;
> +    /* Indicate whether FIFO is enabled or not */
> +    if (FIELD_EX32(s->fcr, FCR, FIFOE)) {
> +        s->iir = FIELD_DP32(s->iir, IIR, FIFOSE, 0x3);
> +    }
> +
> +    if (FIELD_EX32(s->ier, IER, ELSI) &&
> +        (FIELD_EX32(s->lsr, LSR, OE) || FIELD_EX32(s->lsr, LSR, PE) ||
> +         FIELD_EX32(s->lsr, LSR, FE) || FIELD_EX32(s->lsr, LSR, BI))) {
> +        /* Receiver Line Status Interrupt */
> +        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x6);
> +    } else if (FIELD_EX32(s->ier, IER, ERBFI) &&
> +               (s->rx_count >= k230_uart_rx_trigger_level(s))) {
> +        /* Receiver Data Available Interrupt */
> +        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x4);
> +    } else if (FIELD_EX32(s->ier, IER, ERBFI) && s->timeout_ipending) {
> +        /* Character timeout interrupt */
> +        s->iir = FIELD_DP32(s->iir, IIR, IID, 0xc);
> +    } else if (FIELD_EX32(s->ier, IER, ETBEI) && s->thr_ipending) {
> +        /* THR Empty Interrupt */
> +        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x2);
> +    } else if (s->busy_ipending) {
> +        /* Busy detect: LCR written while USR.BUSY=1 (DW-specific) */
> +        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x7);
> +    } else {
> +        s->iir = FIELD_DP32(s->iir, IIR, IID, 0x1);
> +    }
> +}
> +
> +
> +static void k230_uart_update_msr(K230UartState *s)
> +{
> +    uint8_t old_status = s->msr & 0xf0;
> +    uint8_t new_status;
> +
> +    if (FIELD_EX32(s->mcr, MCR, LOOPBACK)) {
> +        /*
> +         * Standard 16550 loopback mapping:
> +         * DTR -> DSR, RTS -> CTS, OUT1 -> RI, OUT2 -> DCD
> +         */
> +        new_status = 0;
> +        if (FIELD_EX32(s->mcr, MCR, RTS)) {
> +            new_status |= 0x10; /* CTS */
> +        }
> +        if (FIELD_EX32(s->mcr, MCR, DTR)) {
> +            new_status |= 0x20; /* DSR */
> +        }
> +        if (FIELD_EX32(s->mcr, MCR, OUT1)) {
> +            new_status |= 0x40; /* RI  */
> +        }
> +        if (FIELD_EX32(s->mcr, MCR, OUT2)) {
> +            new_status |= 0x80; /* DCD */
> +        }
> +    } else {
> +        /* No real modem: report CTS/DSR/DCD as active (ready) */
> +        new_status = 0xb0; /* CTS | DSR | DCD */
> +    }
> +
> +    /* Accumulate delta bits on status changes */
> +    uint8_t deltas = s->msr & 0x0f;
> +    uint8_t changes = old_status ^ new_status;
> +    if (changes & 0x10) {
> +        deltas |= 0x01; /* DCTS */
> +    }
> +    if (changes & 0x20) {
> +        deltas |= 0x02; /* DDSR */
> +    }
> +    if ((old_status & 0x40) && !(new_status & 0x40)) {
> +        deltas |= 0x04; /* TERI */
> +    }
> +    if (changes & 0x80) {
> +        deltas |= 0x08; /* DDCD */
> +    }
> +
> +    s->msr = new_status | deltas;
> +}
> +
> +static void k230_uart_update_all(K230UartState *s)
> +{
> +    k230_uart_update_thre(s);
> +    k230_uart_update_usr(s);
> +    k230_uart_update_iir(s);
> +    k230_uart_update_irq(s);
> +}
> +
> +/* ---- MMIO read ---- */
> +
> +static uint64_t k230_uart_read_rbr(K230UartState *s)
> +{
> +    uint64_t ret = 0;
> +
> +    if (s->rx_count > 0) {
> +        ret = s->rx_fifo[s->rx_tail];
> +        s->rx_tail = (s->rx_tail + 1) % K230_UART_FIFO_DEPTH;
> +        s->rx_count--;
> +        s->timeout_ipending = 0;
> +        if (s->rx_count == 0) {
> +            s->lsr = FIELD_DP32(s->lsr, LSR, DR, 0);
> +            timer_del(&s->rx_timeout);
> +        } else {
> +            timer_mod(&s->rx_timeout,
> +                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
> +                      + 4 * s->char_transmit_time);
> +        }
> +        qemu_chr_fe_accept_input(&s->chr);
> +    }
> +
> +    if (!FIELD_EX32(s->ier, IER, ELCOLR)) {
> +        s->lsr = FIELD_DP32(s->lsr, LSR, OE, 0);
> +        s->lsr = FIELD_DP32(s->lsr, LSR, PE, 0);
> +        s->lsr = FIELD_DP32(s->lsr, LSR, FE, 0);
> +        s->lsr = FIELD_DP32(s->lsr, LSR, BI, 0);
> +    }
> +
> +    k230_uart_update_all(s);
> +    return ret;
> +}
> +
> +static uint64_t k230_uart_read(void *opaque, hwaddr addr, unsigned int size)
> +{
> +    K230UartState *s = K230_UART(opaque);
> +    uint64_t ret = 0;
> +
> +    switch (addr >> 2) {
> +    case R_RBR_DLL_THR:
> +        if (FIELD_EX32(s->lcr, LCR, DLAB)) {
> +            /* DLL accessible only when not busy. */
> +            ret = (K230_UART_16550_COMPATIBLE || !k230_uart_is_busy(s))
> +                  ? s->dll : 0;> +        } else {
> +            ret = k230_uart_read_rbr(s);
> +        }
> +        break;
> +    case R_IER_DLH:
> +        if (FIELD_EX32(s->lcr, LCR, DLAB)) {
> +            /* DLH accessible only when not busy. */
> +            ret = (K230_UART_16550_COMPATIBLE || !k230_uart_is_busy(s))
> +                  ? s->dlh : 0;
> +        } else {
> +            /* IER */
> +            ret = s->ier;
> +        }
> +        break;
> +    case R_IIR:
> +        ret = s->iir;
> +        if (FIELD_EX32(s->iir, IIR, IID) == 0x2) {
> +            s->thr_ipending = 0;
> +            k230_uart_update_all(s);
> +        }
> +        break;
> +    case R_LCR:
> +        ret = s->lcr;
> +        break;
> +    case R_LSR:
> +        ret = s->lsr;
> +        /* Clear OE PE FE and Break Interrupt status bits */
> +        s->lsr = FIELD_DP32(s->lsr, LSR, OE, 0);
> +        s->lsr = FIELD_DP32(s->lsr, LSR, PE, 0);
> +        s->lsr = FIELD_DP32(s->lsr, LSR, FE, 0);
> +        s->lsr = FIELD_DP32(s->lsr, LSR, BI, 0);
> +        k230_uart_update_all(s);
> +        break;
> +    case R_MCR:
> +        ret = s->mcr;
> +        break;
> +    case R_MSR:
> +        ret = s->msr;
> +        s->msr &= 0xf0; /* clear delta bits on read */
> +        break;
> +    case R_SCR:
> +        ret = s->scr;
> +        break;
> +
> +    /* Shadow registers: aliases of standard registers. */
> +    case R_SRTS:
> +        ret = FIELD_EX32(s->mcr, MCR, RTS);
> +        break;
> +    case R_SBCR:
> +        ret = FIELD_EX32(s->lcr, LCR, BC);
> +        break;
> +    case R_SDMAM:
> +        ret = FIELD_EX32(s->fcr, FCR, DMAM);
> +        break;
> +    case R_SFE:
> +        ret = FIELD_EX32(s->fcr, FCR, FIFOE);
> +        break;
> +    case R_SRT:
> +        ret = FIELD_EX32(s->fcr, FCR, RT);
> +        break;
> +    case R_STET:
> +        ret = FIELD_EX32(s->fcr, FCR, TET);
> +        break;
> +    case R_HTX:
> +        ret = s->htx;
> +        break;
> +
> +    case R_CPR:
> +        ret = (K230_UART_APB_DATA_WIDTH          << 0)   |
> +                   (K230_UART_AFCE_MODE               << 4)   |
> +                   (K230_UART_THRE_MODE                << 5)   |
> +                   (K230_UART_SIR_MODE                 << 6)   |
> +                   (K230_UART_SIR_LP_MODE              << 7)   |
> +                   (K230_UART_ADDITIONAL_FEATURES      << 8)   |
> +                   (K230_UART_FIFO_ACCESS              << 9)   |
> +                   (K230_UART_FIFO_STAT                << 10)  |
> +                   (K230_UART_SHADOW                   << 11)  |
> +                   (K230_UART_ADD_ENCODED_PARAMS       << 12)  |
> +                   (K230_UART_DMA_EXTRA                << 13)  |
> +                   (K230_UART_FIFO_MODE                << 16);

These are all hardwired thus I recommend creating a macro in the header like

#define K230_UART_R_CPR (....)

that encapsulates this value.

And while we're at it:


> +        break;
> +    case R_UCV:
> +        /* Version */
> +        ret = 0x342e3061;


Please create a macro for this number.


Thanks,
Daniel

> +        break;
> +    case R_DLF:
> +        ret = 0;
> +        break;
> +    case R_USR:
> +        ret = s->usr;
> +        /* Reading USR clears the busy-detect interrupt. */
> +        if (s->busy_ipending) {
> +            s->busy_ipending = 0;
> +            k230_uart_update_all(s);
> +        }
> +        break;
> +    case R_TFL:
> +        /* Transmit FIFO Level: number of bytes in TX FIFO. */
> +        ret = s->tx_count;
> +        break;
> +    case R_RFL:
> +        /* Receive FIFO Level: number of bytes in RX FIFO. */
> +        ret = s->rx_count;
> +        break;
> +    case R_CTR:
> +        /* Component Type Register: fixed ID. */
> +        ret = K230_UART_CTR_VALUE;
> +        break;
> +    default:
> +        ret = 0;
> +        break;
> +    }
> +    return ret;
> +}
> +
> +/* ---- MMIO write ---- */
> +
> +static void k230_uart_write(void *opaque, hwaddr addr,
> +                                uint64_t val64, unsigned int size)
> +{
> +    K230UartState *s = K230_UART(opaque);
> +    uint32_t val = (uint32_t)val64;
> +    uint32_t offset = addr >> 2;
> +
> +    switch (offset) {
> +    case R_RBR_DLL_THR:
> +        if (FIELD_EX32(s->lcr, LCR, DLAB)) {
> +            /* DLL writable only when not busy. */
> +            if (K230_UART_16550_COMPATIBLE || !k230_uart_is_busy(s)) {
> +                s->dll = val & 0xff;
> +                k230_uart_update_char_time(s);
> +            }
> +        } else {
> +            bool fifo_en = FIELD_EX32(s->fcr, FCR, FIFOE);
> +            uint16_t capacity = fifo_en ? K230_UART_FIFO_DEPTH : 1;
> +            if (s->tx_count < capacity) {
> +                s->tx_fifo[s->tx_head] = val & 0xff;
> +                s->tx_head = (s->tx_head + 1) % K230_UART_FIFO_DEPTH;
> +                s->tx_count++;
> +            } else if (!fifo_en) {
> +                s->tx_fifo[s->tx_tail] = val & 0xff;
> +            }
> +            s->lsr = FIELD_DP32(s->lsr, LSR, THRE, 0);
> +            s->thr_ipending = 0;
> +            s->lsr = FIELD_DP32(s->lsr, LSR, TEMT, 0);
> +            k230_uart_xmit(s);
> +        }
> +        break;
> +    case R_IER_DLH:
> +        if (FIELD_EX32(s->lcr, LCR, DLAB)) {
> +            /* DLH writable only when not busy. */
> +            if (K230_UART_16550_COMPATIBLE || !k230_uart_is_busy(s)) {
> +                s->dlh = val & 0xff;
> +                k230_uart_update_char_time(s);
> +            }
> +        } else {
> +            uint8_t old_etbei = FIELD_EX32(s->ier, IER, ETBEI);
> +            s->ier = FIELD_DP32(s->ier, IER, ERBFI,
> +                                FIELD_EX32(val, IER, ERBFI));
> +            s->ier = FIELD_DP32(s->ier, IER, ETBEI,
> +                                FIELD_EX32(val, IER, ETBEI));
> +            s->ier = FIELD_DP32(s->ier, IER, ELSI,
> +                                FIELD_EX32(val, IER, ELSI));
> +            s->ier = FIELD_DP32(s->ier, IER, EDSSI,
> +                                FIELD_EX32(val, IER, EDSSI));
> +            s->ier = FIELD_DP32(s->ier, IER, ELCOLR,
> +                                FIELD_EX32(val, IER, ELCOLR));
> +#if K230_UART_THRE_MODE
> +            s->ier = FIELD_DP32(s->ier, IER, PTIME,
> +                                FIELD_EX32(val, IER, PTIME));
> +#endif
> +            if (!old_etbei && FIELD_EX32(s->ier, IER, ETBEI)
> +                && FIELD_EX32(s->lsr, LSR, THRE)) {
> +                s->thr_ipending = 1;
> +            }
> +        }
> +        break;
> +    case R_FCR:
> +        if ((val ^ s->fcr) & R_FCR_FIFOE_MASK) {
> +            val |= R_FCR_RFIFOR_MASK | R_FCR_XFIFOR_MASK;
> +        }
> +        s->fcr = FIELD_DP32(s->fcr, FCR, FIFOE,
> +                            FIELD_EX32(val, FCR, FIFOE));
> +        s->fcr = FIELD_DP32(s->fcr, FCR, DMAM,
> +                            FIELD_EX32(val, FCR, DMAM));
> +        s->fcr = FIELD_DP32(s->fcr, FCR, RT,
> +                            FIELD_EX32(val, FCR, RT));
> +#if K230_UART_THRE_MODE
> +        s->fcr = FIELD_DP32(s->fcr, FCR, TET,
> +                            FIELD_EX32(val, FCR, TET));
> +#endif
> +        if (FIELD_EX32(val, FCR, RFIFOR)) {
> +            k230_uart_fifo_reset(s, true, false);
> +        }
> +        if (FIELD_EX32(val, FCR, XFIFOR)) {
> +            k230_uart_fifo_reset(s, false, true);
> +        }
> +        break;
> +    case R_LCR:
> +        /*
> +         * When UART_16550_COMPATIBLE == NO (the K230 case),
> +         * LCR is writable only when USR.BUSY == 0.  A write while busy is
> +         * ignored and triggers the busy-detect interrupt (IID=0x7), which is
> +         * cleared by reading USR.  The 8250_dw driver detects this via
> +         * dw8250_check_lcr() and retries after draining the FIFOs.
> +         */
> +        if (!K230_UART_16550_COMPATIBLE && k230_uart_is_busy(s)) {
> +            s->busy_ipending = 1;
> +        } else {
> +            s->lcr = val & 0xff;
> +        }
> +        break;
> +    case R_MCR:
> +        /*
> +         * K230 advertises AFCE_MODE=0 and SIR_MODE=0 in CPR, so MCR[5:6]
> +         * (AFCE/SIRE) are read-only 0.  MCR[7] is reserved and also 0.
> +         */
> +        s->mcr = val & 0x1f;
> +        k230_uart_update_msr(s);
> +        break;
> +    case R_SCR:
> +        s->scr = val & 0xff;
> +        break;
> +
> +    /*
> +     * Shadow registers: aliases of standard register fields.
> +     * They let software update a single bit without read-modify-write on the
> +     * original register.  SBCR shadows LCR[6] (BC), which is gated by the
> +     * busy-detect logic just like a direct LCR write.
> +     */
> +    case R_SRTS:
> +        s->mcr = FIELD_DP32(s->mcr, MCR, RTS, val & 0x1);
> +        k230_uart_update_msr(s);
> +        break;
> +    case R_SBCR:
> +        if (!K230_UART_16550_COMPATIBLE && k230_uart_is_busy(s)) {
> +            s->busy_ipending = 1;
> +        } else {
> +            s->lcr = FIELD_DP32(s->lcr, LCR, BC, val & 0x1);
> +        }
> +        break;
> +    case R_SDMAM:
> +        s->fcr = FIELD_DP32(s->fcr, FCR, DMAM, val & 0x1);
> +        break;
> +    case R_SFE:
> +        /* Shadow of FCR[0]; changing FIFOE resets both FIFOs */
> +        if ((s->fcr ^ val) & R_FCR_FIFOE_MASK) {
> +            k230_uart_fifo_reset(s, true, true);
> +        }
> +        s->fcr = FIELD_DP32(s->fcr, FCR, FIFOE, val & 0x1);
> +        break;
> +    case R_SRT:
> +        s->fcr = FIELD_DP32(s->fcr, FCR, RT, val & 0x3);
> +        break;
> +    case R_STET:
> +#if K230_UART_THRE_MODE
> +        s->fcr = FIELD_DP32(s->fcr, FCR, TET, val & 0x3);
> +#endif
> +        break;
> +    case R_HTX:
> +        s->htx = val & 0x1;
> +        /* Clearing HTX resumes transmission of any buffered TX data. */
> +        if (!s->htx) {
> +            k230_uart_xmit(s);
> +        }
> +        break;
> +
> +    case R_SRR:
> +        if (FIELD_EX32(val, SRR, UR)) {
> +            device_cold_reset(DEVICE(s));
> +            return;
> +        }
> +        if (FIELD_EX32(val, SRR, RFR)) {
> +            k230_uart_fifo_reset(s, true, false);
> +        }
> +        if (FIELD_EX32(val, SRR, XFR)) {
> +            k230_uart_fifo_reset(s, false, true);
> +        }
> +        break;
> +
> +    default:
> +        break;
> +    }
> +    k230_uart_update_all(s);
> +}
> +
> +/* ---- device lifecycle ---- */
> +
> +static const MemoryRegionOps k230_uart_ops = {
> +    .read = k230_uart_read,
> +    .write = k230_uart_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .impl.min_access_size = 4,
> +    .impl.max_access_size = 4,
> +};
> +
> +static void k230_uart_init(Object *obj)
> +{
> +    K230UartState *s = K230_UART(obj);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
> +
> +    memory_region_init_io(&s->mmio, OBJECT(s), &k230_uart_ops, s,
> +                          TYPE_K230_UART, 0x100);
> +    sysbus_init_mmio(sbd, &s->mmio);
> +    sysbus_init_irq(sbd, &s->irq);
> +}
> +
> +static int k230_uart_can_receive(void *opaque)
> +{
> +    K230UartState *s = K230_UART(opaque);
> +    return K230_UART_FIFO_DEPTH - s->rx_count;
> +}
> +
> +static void k230_uart_receive(void *opaque, const uint8_t *buf, int size)
> +{
> +    K230UartState *s = K230_UART(opaque);
> +    for (int i = 0; i < size; i++) {
> +        k230_uart_push_rx_byte(s, buf[i]);
> +    }
> +}
> +
> +static void k230_uart_event(void *opaque, QEMUChrEvent event)
> +{
> +    K230UartState *s = K230_UART(opaque);
> +
> +    if (event == CHR_EVENT_BREAK) {
> +        k230_uart_push_rx_byte(s, 0);
> +        s->lsr = FIELD_DP32(s->lsr, LSR, BI, 1);
> +        s->lsr = FIELD_DP32(s->lsr, LSR, FE, 1);
> +        k230_uart_update_all(s);
> +    }
> +}
> +
> +static void k230_uart_reset_hold(Object *obj, ResetType type)
> +{
> +    K230UartState *s = K230_UART(obj);
> +    s->lcr = 0;
> +    s->fcr = 0;
> +    s->ier = 0;
> +    s->mcr = 0;
> +    s->scr = 0;
> +    s->dll = 0;
> +    s->dlh = 0;
> +    s->lsr = 0x60;
> +    s->msr = 0xb0;
> +
> +    s->rx_head = s->rx_tail = s->rx_count = 0;
> +    s->tx_head = s->tx_tail = s->tx_count = 0;
> +    s->htx = 0;
> +
> +    qemu_set_irq(s->irq, 0);
> +
> +    s->thr_ipending = 0;
> +    s->timeout_ipending = 0;
> +    s->busy_ipending = 0;
> +    timer_del(&s->rx_timeout);
> +    /* Default to 115200 baud until the driver programs the divisor latch. */
> +    s->char_transmit_time = (NANOSECONDS_PER_SECOND / 115200) * 10;
> +
> +    k230_uart_update_all(s);
> +}
> +
> +static void k230_uart_realize(DeviceState *dev, Error **errp)
> +{
> +    K230UartState *s = K230_UART(dev);
> +    qemu_chr_fe_set_handlers(&s->chr, k230_uart_can_receive,
> +                             k230_uart_receive, k230_uart_event,
> +                             NULL, s, NULL, true);
> +
> +    timer_init_ns(&s->rx_timeout, QEMU_CLOCK_VIRTUAL,
> +                  k230_uart_rx_timeout, s);
> +}
> +
> +static int k230_uart_post_load(void *opaque, int version_id)
> +{
> +    K230UartState *s = K230_UART(opaque);
> +
> +    /*
> +     * iir and usr are derived from the saved state; recompute them after
> +     * migration so the device is consistent.
> +     */
> +    k230_uart_update_all(s);
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_k230_uart = {
> +    .name = "k230.uart",
> +    .version_id = 2,
> +    .minimum_version_id = 1,
> +    .post_load = k230_uart_post_load,
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_UINT8(lcr,   K230UartState),
> +        VMSTATE_UINT8(fcr,   K230UartState),
> +        VMSTATE_UINT8(ier,   K230UartState),
> +        VMSTATE_UINT8(dll,   K230UartState),
> +        VMSTATE_UINT8(dlh,   K230UartState),
> +        VMSTATE_UINT8(mcr,   K230UartState),
> +        VMSTATE_UINT8(lsr,   K230UartState),
> +        VMSTATE_UINT8(msr,   K230UartState),
> +        VMSTATE_UINT8(scr,   K230UartState),
> +        VMSTATE_UINT8(htx,   K230UartState),
> +        VMSTATE_UINT16_ARRAY(rx_fifo, K230UartState,
> +                             K230_UART_FIFO_DEPTH),
> +        VMSTATE_UINT16_ARRAY(tx_fifo, K230UartState,
> +                             K230_UART_FIFO_DEPTH),
> +        VMSTATE_UINT32(rx_head, K230UartState),
> +        VMSTATE_UINT32(rx_tail, K230UartState),
> +        VMSTATE_UINT32(rx_count, K230UartState),
> +        VMSTATE_UINT32(tx_head, K230UartState),
> +        VMSTATE_UINT32(tx_tail, K230UartState),
> +        VMSTATE_UINT32(tx_count, K230UartState),
> +        VMSTATE_UINT8(thr_ipending, K230UartState),
> +        VMSTATE_UINT8(timeout_ipending, K230UartState),
> +        VMSTATE_UINT8(busy_ipending, K230UartState),
> +        VMSTATE_UINT64(char_transmit_time, K230UartState),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static const Property k230_uart_properties[] = {
> +    DEFINE_PROP_CHR("chardev", K230UartState, chr),
> +};
> +
> +static void k230_uart_class_init(ObjectClass *klass, const void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> +    dc->realize = k230_uart_realize;
> +    rc->phases.hold = k230_uart_reset_hold;
> +    dc->vmsd = &vmstate_k230_uart;
> +    dc->desc = "K230 UART (16550-compatible)";
> +    device_class_set_props(dc, k230_uart_properties);
> +}
> +
> +static const TypeInfo k230_uart_info = {
> +    .name = TYPE_K230_UART,
> +    .parent = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(K230UartState),
> +    .instance_init = k230_uart_init,
> +    .class_init = k230_uart_class_init
> +};
> +
> +static void k230_uart_register_types(void)
> +{
> +    type_register_static(&k230_uart_info);
> +}
> +
> +type_init(k230_uart_register_types)
> diff --git a/hw/char/meson.build b/hw/char/meson.build
> index fc3d7ee506fcf8eb1219f6af9689fd90573861c9..23d8ede3f031f80d1b519bf49beac9d23502e2cb 100644
> --- a/hw/char/meson.build
> +++ b/hw/char/meson.build
> @@ -38,6 +38,7 @@ system_ss.add(when: 'CONFIG_STM32L4X5_USART', if_true: files('stm32l4x5_usart.c'
>   system_ss.add(when: 'CONFIG_MCHP_PFSOC_MMUART', if_true: files('mchp_pfsoc_mmuart.c'))
>   system_ss.add(when: 'CONFIG_HTIF', if_true: files('riscv_htif.c'))
>   system_ss.add(when: 'CONFIG_GOLDFISH_TTY', if_true: files('goldfish_tty.c'))
> +system_ss.add(when: 'CONFIG_K230', if_true: files('k230_uart.c'))
>   
>   specific_ss.add(when: 'CONFIG_TERMINAL3270', if_true: files('terminal3270.c'))
>   specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_vty.c'))
> diff --git a/include/hw/char/k230_uart.h b/include/hw/char/k230_uart.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..09407f01bcd00ea6fdc05d054c9a53d4cee3cb98
> --- /dev/null
> +++ b/include/hw/char/k230_uart.h
> @@ -0,0 +1,180 @@
> +/*
> + * K230 UART device
> + *
> + * K230 Technical Reference Manual V0.3.1 (2024-11-18):
> + * https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf
> + *
> + * Register semantics cross-checked against the SDK Linux driver
> + * src/little/linux/drivers/tty/serial/8250/8250_dw.c in
> + * https://github.com/kendryte/k230_sdk (compatible "snps,dw-apb-uart").
> + *
> + * Copyright (c) 2026 WX Chen <wxchen0913@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_K230_UART_H
> +#define HW_K230_UART_H
> +
> +#include "hw/core/sysbus.h"
> +#include "chardev/char-fe.h"
> +#include "hw/core/registerfields.h"
> +#include "qom/object.h"
> +
> +REG32(RBR_DLL_THR, 0x00)
> +REG32(IER_DLH, 0x04)
> +REG32(IER, 0x04)
> +    FIELD(IER, ERBFI, 0, 1)
> +    FIELD(IER, ETBEI, 1, 1)
> +    FIELD(IER, ELSI, 2, 1)
> +    FIELD(IER, EDSSI, 3, 1)
> +    FIELD(IER, ELCOLR, 4, 1)
> +    FIELD(IER, PTIME, 7, 1)
> +REG32(FCR, 0x08)
> +    FIELD(FCR, FIFOE, 0, 1)
> +    FIELD(FCR, RFIFOR, 1, 1)
> +    FIELD(FCR, XFIFOR, 2, 1)
> +    FIELD(FCR, DMAM, 3, 1)
> +    FIELD(FCR, TET, 4, 2)
> +    FIELD(FCR, RT, 6, 2)
> +REG32(IIR, 0x08)
> +    FIELD(IIR, IID, 0, 4)
> +    FIELD(IIR, FIFOSE, 6, 2)
> +REG32(LCR, 0x0c)
> +    FIELD(LCR, DLS, 0, 2)
> +    FIELD(LCR, STOP, 2, 1)
> +    FIELD(LCR, PEN, 3, 1)
> +    FIELD(LCR, EPS, 4, 1)
> +    FIELD(LCR, SP, 5, 1)
> +    FIELD(LCR, BC, 6, 1)
> +    FIELD(LCR, DLAB, 7, 1)
> +REG32(MCR, 0x10)
> +    FIELD(MCR, DTR, 0, 1)
> +    FIELD(MCR, RTS, 1, 1)
> +    FIELD(MCR, OUT1, 2, 1)
> +    FIELD(MCR, OUT2, 3, 1)
> +    FIELD(MCR, LOOPBACK, 4, 1)
> +    FIELD(MCR, AFCE, 5, 1)
> +    FIELD(MCR, SIRE, 6, 1)
> +REG32(LSR, 0x14)
> +    FIELD(LSR, DR, 0, 1)
> +    FIELD(LSR, OE, 1, 1)
> +    FIELD(LSR, PE, 2, 1)
> +    FIELD(LSR, FE, 3, 1)
> +    FIELD(LSR, BI, 4, 1)
> +    FIELD(LSR, THRE, 5, 1)
> +    FIELD(LSR, TEMT, 6, 1)
> +    FIELD(LSR, RFE, 7, 1)
> +    FIELD(LSR, ADDR_RSVD, 8, 1)
> +REG32(MSR, 0x18)
> +    FIELD(MSR, DCTS, 0, 1)
> +    FIELD(MSR, DDSR, 1, 1)
> +    FIELD(MSR, TERI, 2, 1)
> +    FIELD(MSR, DDCD, 3, 1)
> +    FIELD(MSR, CTS, 4, 1)
> +    FIELD(MSR, DSR, 5, 1)
> +    FIELD(MSR, RI, 6, 1)
> +    FIELD(MSR, DCD, 7, 1)
> +REG32(RFW, 0x78)
> +    FIELD(RFW, RFWD, 0, 8)
> +    FIELD(RFW, RFPE, 8, 1)
> +    FIELD(RFW, RFFE, 9, 1)
> +REG32(USR, 0x7c)
> +    FIELD(USR, BUSY, 0, 1)
> +    FIELD(USR, TFNF, 1, 1)
> +    FIELD(USR, TFE, 2, 1)
> +    FIELD(USR, RFNE, 3, 1)
> +    FIELD(USR, RFF, 4, 1)
> +REG32(TFL, 0x80)
> +    FIELD(TFL, TFL, 0, 5)
> +REG32(RFL, 0x84)
> +    FIELD(RFL, RFL, 0, 5)
> +REG32(SRR, 0x88)
> +    FIELD(SRR, UR, 0, 1)
> +    FIELD(SRR, RFR, 1, 1)
> +    FIELD(SRR, XFR, 2, 1)
> +REG32(SRTS, 0x8c)
> +    FIELD(SRTS, SRTS, 0, 1)
> +REG32(SBCR, 0x90)
> +    FIELD(SBCR, SBCB, 0, 1)
> +REG32(SDMAM, 0x94)
> +    FIELD(SDMAM, SDMAM, 0, 1)
> +REG32(SFE, 0x98)
> +    FIELD(SFE, SFE, 0, 1)
> +REG32(SRT, 0x9c)
> +    FIELD(SRT, SRT, 0, 2)
> +REG32(STET, 0xa0)
> +    FIELD(STET, STET, 0, 2)
> +REG32(HTX, 0xa4)
> +    FIELD(HTX, HTX, 0, 1)
> +REG32(TCR, 0xac)
> +    FIELD(TCR, RS485_EN, 0, 1)
> +    FIELD(TCR, RE_POL, 1, 1)
> +    FIELD(TCR, DE_POL, 2, 1)
> +    FIELD(TCR, XFER_MODE, 3, 2)
> +REG32(SCR, 0x1c)
> +REG32(DLF, 0xc0)
> +REG32(CPR, 0xf4)
> +REG32(UCV, 0xf8)
> +REG32(CTR, 0xfc)
> +
> +/* peripheral ID 0x44570110 ("DW\x01\x10").  Read-only. */
> +#define K230_UART_CTR_VALUE  0x44570110u
> +#define K230_UART_16550_COMPATIBLE    0
> +#define K230_UART_FIFO_DEPTH          32
> +
> +/* CPR */
> +#define K230_UART_APB_DATA_WIDTH      2      /* CPR[1:0]  - 32-bit APB       */
> +#define K230_UART_AFCE_MODE           0      /* CPR[4]    - not implemented */
> +#define K230_UART_THRE_MODE           1      /* CPR[5]    - implemented     */
> +#define K230_UART_SIR_MODE            0      /* CPR[6]    - not implemented */
> +#define K230_UART_SIR_LP_MODE         0      /* CPR[7]    - not implemented */
> +#define K230_UART_ADDITIONAL_FEATURES 1      /* CPR[8]    - UCV/CTR present */
> +#define K230_UART_FIFO_ACCESS         0      /* CPR[9]    - not implemented */
> +#define K230_UART_FIFO_STAT           1      /* CPR[10]   - TFL/RFL present */
> +#define K230_UART_SHADOW              1      /* CPR[11]   - shadow regs     */
> +#define K230_UART_ADD_ENCODED_PARAMS  1      /* CPR[12]   - CPR present     */
> +#define K230_UART_DMA_EXTRA           0      /* CPR[13]   - not implemented */
> +#define K230_UART_FIFO_MODE           0x2    /* CPR[23:16] - 32-byte FIFO   */
> +
> +#define TYPE_K230_UART "k230-uart"
> +OBJECT_DECLARE_SIMPLE_TYPE(K230UartState, K230_UART)
> +struct K230UartState {
> +    SysBusDevice parent_obj;
> +    MemoryRegion mmio;
> +
> +    /* Standard 16550 registers */
> +    uint8_t  dll;       /* Divisor Latch Low,               offset 0x00 */
> +    uint8_t  ier;       /* Interrupt Enable,                offset 0x04 */
> +    uint8_t  dlh;       /* Divisor Latch High,              offset 0x04 */
> +    uint8_t  fcr;       /* FIFO Control,                    offset 0x08 */
> +    uint8_t  iir;       /* Interrupt Identification,        offset 0x08 */
> +    uint8_t  lcr;       /* Line Control,                    offset 0x0c */
> +    uint8_t  mcr;       /* Modem Control,                   offset 0x10 */
> +    uint8_t  lsr;       /* Line Status,                     offset 0x14 */
> +    uint8_t  msr;       /* Modem Status,                    offset 0x18 */
> +    uint8_t  scr;       /* Scratchpad,                      offset 0x1c */
> +
> +    /* DesignWare-specific registers */
> +    uint8_t  usr;       /* UART Status,                     offset 0x7c */
> +    uint8_t  htx;       /* Halt TX,                         offset 0xa4 */
> +
> +    /* Internal interrupt state. */
> +    uint8_t  thr_ipending;       /* THR empty (IID=0x2) pending     */
> +    uint8_t  timeout_ipending;   /* RX FIFO timeout (IID=0xc)       */
> +    uint8_t  busy_ipending;      /* busy detect (IID=0x7) pending   */
> +
> +    /* FIFO */
> +    uint16_t rx_fifo[K230_UART_FIFO_DEPTH];
> +    uint32_t rx_head, rx_tail, rx_count;
> +    uint16_t tx_fifo[K230_UART_FIFO_DEPTH];
> +    uint32_t tx_head, tx_tail, tx_count;
> +
> +    uint64_t char_transmit_time;
> +
> +    CharFrontend chr;
> +    qemu_irq    irq;
> +    QEMUTimer rx_timeout;
> +};
> +
> +#endif
> 



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH RESEND v2 2/3] hw/riscv: k230: connect DW 8250 UART
  2026-07-25  3:52 ` [PATCH RESEND v2 2/3] hw/riscv: k230: connect DW 8250 UART WX Chen
@ 2026-08-05 20:02   ` Daniel Henrique Barboza
  0 siblings, 0 replies; 9+ messages in thread
From: Daniel Henrique Barboza @ 2026-08-05 20:02 UTC (permalink / raw)
  To: WX Chen, qemu-devel
  Cc: Paolo Bonzini, Marc-André Lureau, Chao Liu, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Liu Zhiwei, qemu-riscv,
	Fabiano Rosas, Laurent Vivier



On 7/25/2026 12:52 AM, WX Chen wrote:
> Replace the generic serial-mm device with the K230 DW 8250-compatible
> UART model added in the previous patch.
> 
> Signed-off-by: WX Chen <wxchen0913@gmail.com>
> ---

Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>

>   hw/riscv/k230.c         | 33 ++++++++++++++++++++++-----------
>   include/hw/riscv/k230.h |  6 ++++--
>   2 files changed, 26 insertions(+), 13 deletions(-)
> 
> diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
> index 502281c52cff1dce6febb7487dfdefefe6363e9c..d49fa23448f38dfed45a570eb5bece64519bfe27 100644
> --- a/hw/riscv/k230.c
> +++ b/hw/riscv/k230.c
> @@ -29,7 +29,6 @@
>   #include "hw/riscv/machines-qom.h"
>   #include "hw/intc/riscv_aclint.h"
>   #include "hw/intc/sifive_plic.h"
> -#include "hw/char/serial-mm.h"
>   #include "hw/misc/unimp.h"
>   
>   /* Align K230_SDK k230_canmv_defconfig */
> @@ -111,6 +110,11 @@ static void k230_soc_init(Object *obj)
>       object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT);
>       object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT);
>   
> +    for (int i = 0; i < K230_UART_COUNT; i++) {
> +        g_autofree char *name = g_strdup_printf("k230-uart%d", i);
> +        object_initialize_child(obj, name, &s->uart[i], TYPE_K230_UART);
> +    }
> +
>       qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
>       qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
>       qdev_prop_set_uint64(DEVICE(cpu0), "resetvec",
> @@ -136,19 +140,26 @@ static DeviceState *k230_create_plic(int base_hartid, int hartid_count)
>                                 memmap[K230_DEV_PLIC].size);
>   }
>   
> -static void k230_create_uart(MemoryRegion *sys_mem, DeviceState *plic,
> -                             int index)
> +static void k230_create_uart(K230SoCState *s, DeviceState *plic, int index)
>   {
>       int uart_dev = K230_DEV_UART0 + index;
> -    g_autofree char *name = g_strdup_printf("uart%d", index);
> +    g_autofree char *unimpl_name = g_strdup_printf("uart%d", index);
> +    DeviceState *dev = DEVICE(&s->uart[index]);
>   
> -    /* Cover the non-16550 part of the SDK's 0x1000 UART window. */
> -    create_unimplemented_device(name, memmap[uart_dev].base,
> -                                memmap[uart_dev].size);
> +    qdev_prop_set_chr(dev, "chardev", serial_hd(index));
>   
> -    serial_mm_init(sys_mem, memmap[uart_dev].base, 2,
> -                   qdev_get_gpio_in(plic, K230_UART0_IRQ + index),
> -                   399193, serial_hd(index), DEVICE_LITTLE_ENDIAN);
> +    if (!sysbus_realize(SYS_BUS_DEVICE(dev), &error_fatal)) {
> +        return;
> +    }
> +
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, memmap[uart_dev].base);
> +    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
> +                       qdev_get_gpio_in(plic, K230_UART0_IRQ + index));
> +
> +    /* Cover the non-16550 part of the SDK's 0x1000 UART window. */
> +    create_unimplemented_device(unimpl_name,
> +                                memmap[uart_dev].base + 0x100,
> +                                memmap[uart_dev].size - 0x100);
>   }
>   
>   static void k230_soc_realize(DeviceState *dev, Error **errp)
> @@ -188,7 +199,7 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
>   
>       /* UART */
>       for (int i = 0; i < K230_UART_COUNT; i++) {
> -        k230_create_uart(sys_mem, DEVICE(s->c908_plic), i);
> +        k230_create_uart(s, DEVICE(s->c908_plic), i);
>       }
>   
>       /* Watchdog */
> diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
> index 592e1c26bf8a8f8a1c66653ff9b568fbfe98a3ea..fed0357c2b9e1f5254cb157e0cd4d75d4946d1a1 100644
> --- a/include/hw/riscv/k230.h
> +++ b/include/hw/riscv/k230.h
> @@ -17,10 +17,13 @@
>   
>   #include "hw/core/boards.h"
>   #include "hw/riscv/riscv_hart.h"
> +#include "hw/char/k230_uart.h"
>   #include "hw/watchdog/k230_wdt.h"
>   
>   #define C908_CPU_HARTID   (0)
>   
> +#define K230_UART_COUNT 5
> +
>   #define TYPE_RISCV_K230_SOC "riscv.k230.soc"
>   #define RISCV_K230_SOC(obj) \
>       OBJECT_CHECK(K230SoCState, (obj), TYPE_RISCV_K230_SOC)
> @@ -32,6 +35,7 @@ typedef struct K230SoCState {
>       /*< public >*/
>       RISCVHartArrayState c908_cpu; /* Small core */
>   
> +    K230UartState uart[K230_UART_COUNT];
>       K230WdtState wdt[2];
>       MemoryRegion sram;
>       MemoryRegion bootrom;
> @@ -131,8 +135,6 @@ enum {
>       K230_WDT1_IRQ   = 108,
>   };
>   
> -#define K230_UART_COUNT 5
> -
>   /*
>    * Integrates with the interrupt controller (PLIC),
>    * which can process 208 interrupt external sources
> 



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH RESEND v2 3/3] tests/qtest: add K230 UART test
  2026-07-25  3:52 ` [PATCH RESEND v2 3/3] tests/qtest: add K230 UART test WX Chen
@ 2026-08-05 20:10   ` Daniel Henrique Barboza
  2026-08-07 11:53     ` WX Chen
  0 siblings, 1 reply; 9+ messages in thread
From: Daniel Henrique Barboza @ 2026-08-05 20:10 UTC (permalink / raw)
  To: WX Chen, qemu-devel
  Cc: Paolo Bonzini, Marc-André Lureau, Chao Liu, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Liu Zhiwei, qemu-riscv,
	Fabiano Rosas, Laurent Vivier



On 7/25/2026 12:52 AM, WX Chen wrote:
> Add 8 test cases covering the main register paths and driver usage
> scenarios: device probe, init & baud, TX/RX datapath, THRE interrupt,
> RX interrupts (timeout & trigger level), error interrupts & IIR
> priority, USR & busy detect, and advanced features (shadow registers,
> HTX, SRR, PTIME/TET).
> 
> All tests pass.
> 
> Signed-off-by: WX Chen <wxchen0913@gmail.com>
> ---

This also LGTM with a minor observation down there:



>   tests/qtest/k230-uart-test.c | 514 +++++++++++++++++++++++++++++++++++++++++++
>   tests/qtest/meson.build      |   2 +-
>   2 files changed, 515 insertions(+), 1 deletion(-)
> 
> diff --git a/tests/qtest/k230-uart-test.c b/tests/qtest/k230-uart-test.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..f85c690ebb57abb5b7f753f36c06c0900c3cfbb9
> --- /dev/null
> +++ b/tests/qtest/k230-uart-test.c
> @@ -0,0 +1,514 @@
> +/*
> + * QTest for the K230 UART — functional-path coverage.
> + *
> + * Tests are organised around driver usage scenarios rather than
> + * enumerating every register in isolation.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest.h"
> +#include <string.h>
> +
> +#define UART_BASE  0x91400000
> +#define R(off)     (UART_BASE + (off))
> +
> +/* register offsets */
> +#define THR    0x00
> +#define IER    0x04
> +#define IIR    0x08
> +#define LCR    0x0c
> +#define MCR    0x10
> +#define LSR    0x14
> +#define SCR    0x1c
> +#define USR    0x7c
> +#define TFL    0x80
> +#define RFL    0x84
> +#define SRR    0x88
> +#define SRTS   0x8c
> +#define SBCR   0x90
> +#define SDMAM  0x94
> +#define SFE    0x98
> +#define SRT    0x9c
> +#define STET   0xa0
> +#define HTX    0xa4
> +#define CPR    0xf4
> +#define CTR    0xfc
> +
> +/* bit fields */
> +#define LCR_DLAB     0x80
> +#define LCR_BC       0x40
> +#define LCR_8N1      0x03
> +
> +#define LSR_DR       0x01
> +#define LSR_OE       0x02
> +#define LSR_THRE     0x20
> +#define LSR_TEMT     0x40
> +#define LSR_RESET    0x60
> +
> +#define IIR_IID      0x0f
> +#define IIR_NONE     0x01
> +#define IIR_THR      0x02
> +#define IIR_RX       0x04
> +#define IIR_LINE     0x06
> +#define IIR_BUSY     0x07
> +#define IIR_TO       0x0c
> +#define IIR_FF       0xc0
> +
> +#define IER_RX       0x01
> +#define IER_TX       0x02
> +#define IER_LS       0x04
> +#define IER_COLR     0x10
> +#define IER_PTIME    0x80
> +
> +#define MCR_LB       0x10
> +#define MCR_RTS      0x02
> +
> +#define FCR_FE       0x01
> +#define FCR_RR       0x02
> +#define FCR_XR       0x04
> +#define FCR_TET_H    (3 << 4)
> +#define FCR_RT_Q     (1 << 6)
> +#define FCR_RT_F     (3 << 6)
> +
> +#define USR_BUSY     0x01
> +#define USR_RESET    0x06
> +
> +#define SRR_UR       0x01
> +#define SRR_RFR      0x02
> +
> +/* helpers */
> +static uint32_t rd(QTestState *qts, uint32_t o)
> +{
> +    return qtest_readl(qts, R(o));
> +}
> +static uint32_t iid(QTestState *qts)
> +{
> +    return rd(qts, IIR) & IIR_IID;
> +}
> +
> +static void poll_lsr(QTestState *qts, uint32_t m)
> +{
> +    int i;
> +
> +    for (i = 0; i < 1000; i++) {
> +        if (rd(qts, LSR) & m) {
> +            return;
> +        }
> +        g_usleep(1000);

Any reason to use g_usleep() instead of qtest_clock_step()?  There are a couple
of g_usleep() uses in the code but you used lots of qtest_clock_step() too.

AFAIK g_usleep() is wall time and qtest_clock_step() is simulated time, and
I guess you should be using qtest_clock_step() for all the pollings you're
doing in this file.


Thanks,
Daniel


> +    }
> +    g_assert_not_reached();
> +}
> +
> +static void s1(QTestState *qts, int fd, char c)
> +{
> +    g_assert_cmpint(send(fd, &c, 1, 0), ==, 1);
> +    poll_lsr(qts, LSR_DR);
> +}
> +
> +static void sn(QTestState *qts, int fd, const char *d, int n)
> +{
> +    g_assert_cmpint(send(fd, d, n, 0), ==, n);
> +    poll_lsr(qts, LSR_DR);
> +}
> +
> +static void oe_nf(QTestState *qts, int fd)
> +{
> +    int i;
> +
> +    s1(qts, fd, 'A');
> +    {
> +        char c = 'B';
> +        g_assert_cmpint(send(fd, &c, 1, 0), ==, 1);
> +    }
> +    for (i = 0; i < 200; i++) {
> +        rd(qts, SCR);
> +        g_usleep(1000);
> +    }
> +}
> +
> +/* 1. device probe */
> +static void test_device_probe(void)
> +{
> +    QTestState *qts = qtest_init("-machine k230");
> +    uint32_t cpr = rd(qts, CPR);
> +
> +    g_assert_cmphex(cpr & 0x3, ==, 0x2);
> +    g_assert_cmphex(cpr & (1 << 5), ==, (1 << 5));
> +    g_assert_cmphex(cpr & (1 << 8), ==, (1 << 8));
> +    g_assert_cmphex((cpr >> 16) & 0xff, ==, 0x2);
> +    g_assert_cmphex(cpr & (1 << 4), ==, 0);
> +    g_assert_cmphex(rd(qts, CTR), ==, 0x44570110);
> +    g_assert_cmphex(rd(qts, LSR), ==, LSR_RESET);
> +    g_assert_cmphex(rd(qts, USR), ==, USR_RESET);
> +    g_assert_cmphex(iid(qts), ==, IIR_NONE);
> +    g_assert_cmphex(rd(qts, IIR) & IIR_FF, ==, 0);
> +    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
> +
> +    qtest_writel(qts, R(IIR), FCR_FE);
> +    g_assert_cmphex(rd(qts, IIR) & IIR_FF, ==, IIR_FF);
> +    qtest_quit(qts);
> +}
> +
> +/* 2. init & baud */
> +static void test_init_and_baud(void)
> +{
> +    int fd;
> +    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
> +
> +    qtest_writel(qts, R(LCR), LCR_DLAB);
> +    qtest_writel(qts, R(THR), 0x55);
> +    g_assert_cmphex(rd(qts, THR), ==, 0x55);
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +    g_assert_cmphex(rd(qts, THR), ==, 0x00);
> +
> +    qtest_writel(qts, R(IIR), FCR_FE | FCR_RT_F);
> +    qtest_writel(qts, R(IER), IER_RX);
> +
> +    /* divisor=1 -> timeout=12800ns */
> +    qtest_writel(qts, R(LCR), LCR_8N1 | LCR_DLAB);
> +    qtest_writel(qts, R(THR), 1); qtest_writel(qts, R(IER), 0);
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +    s1(qts, fd, 'X');
> +    qtest_clock_step(qts, 13000);
> +    g_assert_cmphex(iid(qts), ==, IIR_TO);
> +
> +    /* divisor=100 -> timeout=1.28e6ns; 13000ns too short */
> +    rd(qts, THR);
> +    qtest_writel(qts, R(LCR), LCR_8N1 | LCR_DLAB);
> +    qtest_writel(qts, R(THR), 100); qtest_writel(qts, R(IER), 0);
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +    s1(qts, fd, 'Y');
> +    qtest_clock_step(qts, 13000);
> +    g_assert_cmphex(iid(qts), !=, IIR_TO);
> +    qtest_clock_step(qts, 1300000);
> +    g_assert_cmphex(iid(qts), ==, IIR_TO);
> +
> +    close(fd); qtest_quit(qts);
> +}
> +
> +/* 3. TX / RX datapath */
> +static void test_tx_rx_datapath(void)
> +{
> +    int fd;
> +    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
> +
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +    g_assert_cmphex(rd(qts, LSR) & (LSR_THRE | LSR_TEMT),
> +                    ==, LSR_THRE | LSR_TEMT);
> +    qtest_writel(qts, R(THR), 'A');
> +    g_assert_cmphex(rd(qts, LSR) & (LSR_THRE | LSR_TEMT),
> +                    ==, LSR_THRE | LSR_TEMT);
> +
> +    /* external RX (FIFO mode) */
> +    qtest_writel(qts, R(IIR), FCR_FE);
> +    qtest_writel(qts, R(IER), IER_RX);
> +    sn(qts, fd, "K230", 4);
> +    for (int i = 0; i < 4; i++) {
> +        if (i < 3) {
> +            g_assert_cmphex(iid(qts), ==, IIR_RX);
> +        }
> +        g_assert_cmphex(rd(qts, THR), ==, "K230"[i]);
> +    }
> +    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
> +
> +    /* loopback */
> +    qtest_writel(qts, R(MCR), MCR_LB);
> +    qtest_writel(qts, R(THR), 'L');
> +    g_assert_cmphex(rd(qts, THR), ==, 'L');
> +    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
> +
> +    /* no loopback */
> +    qtest_writel(qts, R(MCR), 0);
> +    qtest_writel(qts, R(THR), 'Z');
> +    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
> +
> +    /* TFL / RFL */
> +    qtest_writel(qts, R(MCR), MCR_LB);
> +    qtest_writel(qts, R(IIR), FCR_FE | FCR_RR | FCR_XR);
> +    qtest_writel(qts, R(THR), 'X'); qtest_writel(qts, R(THR), 'Y');
> +    g_assert_cmphex(rd(qts, TFL), ==, 0);
> +    g_assert_cmphex(rd(qts, RFL), ==, 2);
> +
> +    /* non-FIFO mode */
> +    qtest_writel(qts, R(MCR), 0);
> +    qtest_writel(qts, R(IIR), 0);
> +    s1(qts, fd, 'N');
> +    g_assert_cmphex(rd(qts, THR), ==, 'N');
> +    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
> +
> +    /* non-FIFO overrun */
> +    int i;
> +    s1(qts, fd, 'n');
> +    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, LSR_DR);
> +    {
> +        char c = 'o';
> +        send(fd, &c, 1, 0);
> +    }
> +    for (i = 0; i < 200; i++) {
> +        rd(qts, SCR);
> +        g_usleep(1000);
> +    }
> +    g_assert_cmphex(rd(qts, LSR) & LSR_OE, ==, LSR_OE);
> +
> +    close(fd); qtest_quit(qts);
> +}
> +
> +/* 4. THRE interrupt */
> +static void test_thre_interrupt(void)
> +{
> +    QTestState *qts = qtest_init("-machine k230 "
> +                                 "-chardev null,id=c0 -serial chardev:c0");
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +
> +    qtest_writel(qts, R(THR), 'A');
> +    g_assert_cmphex(iid(qts), ==, IIR_NONE);
> +
> +    qtest_writel(qts, R(IER), IER_TX);
> +    g_assert_cmphex(iid(qts), ==, IIR_THR);
> +
> +    qtest_writel(qts, R(THR), 'B');
> +    g_assert_cmphex(iid(qts), ==, IIR_THR);
> +
> +    g_assert_cmphex(iid(qts), ==, IIR_NONE);
> +    qtest_quit(qts);
> +}
> +
> +/* 5. RX interrupts: timeout & trigger level */
> +static void test_rx_interrupts(void)
> +{
> +    int fd;
> +    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
> +
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +    qtest_writel(qts, R(IIR), FCR_FE | FCR_RT_F);
> +    qtest_writel(qts, R(IER), IER_RX);
> +
> +    /* basic timeout */
> +    s1(qts, fd, 'T');
> +    qtest_clock_step(qts, 400000);
> +    g_assert_cmphex(iid(qts), ==, IIR_TO);
> +    g_assert_cmphex(rd(qts, THR), ==, 'T');
> +    g_assert_cmphex(iid(qts), !=, IIR_TO);
> +
> +    /* timeout reset by new byte */
> +    s1(qts, fd, 'A'); qtest_clock_step(qts, 200000);
> +    s1(qts, fd, 'B');
> +    qtest_clock_step(qts, 200000);
> +    g_assert_cmphex(iid(qts), ==, IIR_NONE);
> +    qtest_clock_step(qts, 200000);
> +    g_assert_cmphex(iid(qts), ==, IIR_TO);
> +    rd(qts, THR);
> +    rd(qts, THR);
> +
> +    /* timeout rearmed by partial drain */
> +    s1(qts, fd, 'X');
> +    s1(qts, fd, 'Y');
> +    qtest_clock_step(qts, 400000);
> +    g_assert_cmphex(iid(qts), ==, IIR_TO);
> +    g_assert_cmphex(rd(qts, THR), ==, 'X');
> +    qtest_clock_step(qts, 200000);
> +    g_assert_cmphex(iid(qts), !=, IIR_TO);
> +    qtest_clock_step(qts, 250000);
> +    g_assert_cmphex(iid(qts), ==, IIR_TO);
> +    g_assert_cmphex(rd(qts, THR), ==, 'Y');
> +
> +    /* RX trigger level: RT=Q -> trigger at 8 bytes */
> +    qtest_writel(qts, R(IIR), FCR_FE | FCR_RT_Q);
> +    sn(qts, fd, "ABCDEFG", 7);
> +    g_usleep(20000);
> +    g_assert_cmphex(iid(qts), ==, IIR_NONE);
> +    qtest_clock_step(qts, 400000);
> +    g_assert_cmphex(iid(qts), ==, IIR_TO);
> +    for (int i = 0; i < 7; i++) {
> +        rd(qts, THR);
> +    }
> +
> +    sn(qts, fd, "12345678", 8);
> +    for (int i = 0; i < 1000; i++) {
> +        if (iid(qts) == IIR_RX) {
> +            break;
> +        }
> +        g_usleep(1000);
> +    }
> +    g_assert_cmphex(iid(qts), ==, IIR_RX);
> +
> +    close(fd); qtest_quit(qts);
> +}
> +
> +/* 6. error interrupts & IIR priority */
> +static void test_error_interrupts(void)
> +{
> +    int fd;
> +    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
> +
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +    qtest_writel(qts, R(IER), IER_LS);
> +
> +    /* OE -> IIR=0x6; LSR clears; ELCOLR=0: RBR clears; ELCOLR=1: RBR keeps */
> +    oe_nf(qts, fd);
> +    g_assert_cmphex(iid(qts), ==, IIR_LINE);
> +    rd(qts, LSR);
> +    g_assert_cmphex(iid(qts), !=, IIR_LINE);
> +
> +    oe_nf(qts, fd);
> +    g_assert_cmphex(iid(qts), ==, IIR_LINE);
> +    rd(qts, THR);
> +    g_assert_cmphex(iid(qts), !=, IIR_LINE);
> +
> +    qtest_writel(qts, R(IER), IER_LS | IER_COLR);
> +    oe_nf(qts, fd);
> +    g_assert_cmphex(iid(qts), ==, IIR_LINE);
> +    rd(qts, THR);
> +    g_assert_cmphex(iid(qts), ==, IIR_LINE);
> +    rd(qts, LSR);
> +    g_assert_cmphex(iid(qts), !=, IIR_LINE);
> +
> +    /* IIR priority: RX > TX (loopback) */
> +    qtest_writel(qts, R(IIR), FCR_FE);
> +    qtest_writel(qts, R(MCR), MCR_LB);
> +    qtest_writel(qts, R(IER), IER_TX | IER_RX);
> +    qtest_writel(qts, R(THR), 'P');
> +    g_assert_cmphex(iid(qts), ==, IIR_RX);
> +    g_assert_cmphex(rd(qts, THR), ==, 'P');
> +    g_assert_cmphex(iid(qts), ==, IIR_THR);
> +    iid(qts);
> +    g_assert_cmphex(iid(qts), ==, IIR_NONE);
> +
> +    /* OE in FIFO mode via loopback */
> +    qtest_writel(qts, R(IER), IER_LS);
> +    for (int i = 0; i < 32; i++) {
> +        qtest_writel(qts, R(THR), 'a');
> +    }
> +    g_assert_cmphex(rd(qts, LSR) & LSR_OE, ==, 0);
> +    qtest_writel(qts, R(THR), 'z');
> +    g_assert_cmphex(iid(qts), ==, IIR_LINE);
> +    g_assert_cmphex(rd(qts, LSR) & LSR_OE, ==, LSR_OE);
> +
> +    close(fd); qtest_quit(qts);
> +}
> +
> +/* 7. USR & busy detect */
> +static void test_busy_detect(void)
> +{
> +    int fd;
> +    QTestState *qts = qtest_init_with_serial("-machine k230", &fd);
> +
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +    qtest_writel(qts, R(IIR), FCR_FE);
> +
> +    g_assert_cmphex(rd(qts, USR) & 0x1e, ==, 0x06);
> +
> +    qtest_writel(qts, R(MCR), MCR_LB);
> +    qtest_writel(qts, R(THR), 'A');
> +    g_assert_cmphex(rd(qts, USR) & 0x08, ==, 0x08);
> +    rd(qts, THR);
> +    g_assert_cmphex(rd(qts, USR) & 0x08, ==, 0);
> +    qtest_writel(qts, R(MCR), 0);
> +
> +    /* BUSY via RX & loopback */
> +    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
> +    s1(qts, fd, 'Z');
> +    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, USR_BUSY);
> +    rd(qts, THR);
> +    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
> +
> +    qtest_writel(qts, R(MCR), MCR_LB);
> +    qtest_writel(qts, R(THR), 'L');
> +    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, USR_BUSY);
> +    rd(qts, THR);
> +    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
> +
> +    /* busy-detect: LCR write rejected while BUSY=1 */
> +    qtest_writel(qts, R(MCR), 0);
> +    qtest_writel(qts, R(IIR), FCR_FE | FCR_RR | FCR_XR);
> +    s1(qts, fd, 'B');
> +    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, USR_BUSY);
> +    qtest_writel(qts, R(LCR), LCR_8N1 | LCR_DLAB);
> +    g_assert_cmphex(rd(qts, LCR), ==, LCR_8N1);
> +    g_assert_cmphex(iid(qts), ==, IIR_BUSY);
> +    rd(qts, USR);
> +    g_assert_cmphex(iid(qts), ==, IIR_NONE);
> +    qtest_writel(qts, R(IIR), FCR_FE | FCR_RR);
> +    g_assert_cmphex(rd(qts, USR) & USR_BUSY, ==, 0);
> +    qtest_writel(qts, R(LCR), LCR_8N1 | LCR_DLAB);
> +    g_assert_cmphex(rd(qts, LCR), ==, LCR_8N1 | LCR_DLAB);
> +
> +    close(fd); qtest_quit(qts);
> +}
> +
> +/* 8. advanced features */
> +static void test_advanced_features(void)
> +{
> +    QTestState *qts = qtest_init("-machine k230 "
> +                                 "-chardev null,id=c0 -serial chardev:c0");
> +
> +    /* shadow: SRTS<->MCR.RTS, SBCR<->LCR.BC, SDMAM, SFE, SRT, STET, HTX */
> +    qtest_writel(qts, R(SRTS), 1);
> +    g_assert_cmphex(rd(qts, MCR) & MCR_RTS, ==, MCR_RTS);
> +    qtest_writel(qts, R(MCR), 0); g_assert_cmphex(rd(qts, SRTS), ==, 0);
> +    qtest_writel(qts, R(SBCR), 1);
> +    g_assert_cmphex(rd(qts, LCR) & LCR_BC, ==, LCR_BC);
> +    qtest_writel(qts, R(LCR), 0); g_assert_cmphex(rd(qts, SBCR), ==, 0);
> +    qtest_writel(qts, R(SDMAM), 1); g_assert_cmphex(rd(qts, SDMAM), ==, 1);
> +    qtest_writel(qts, R(SRT), 0x2); g_assert_cmphex(rd(qts, SRT), ==, 0x2);
> +    qtest_writel(qts, R(STET), 0x3); g_assert_cmphex(rd(qts, STET), ==, 0x3);
> +    qtest_writel(qts, R(SFE), 1); g_assert_cmphex(rd(qts, SFE), ==, 1);
> +    g_assert_cmphex(rd(qts, IIR) & IIR_FF, ==, IIR_FF);
> +    qtest_writel(qts, R(HTX), 1); g_assert_cmphex(rd(qts, HTX), ==, 1);
> +    qtest_writel(qts, R(HTX), 0); g_assert_cmphex(rd(qts, HTX), ==, 0);
> +
> +    /* HTX halt TX */
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +    qtest_writel(qts, R(IIR), FCR_FE);
> +    qtest_writel(qts, R(MCR), MCR_LB);
> +
> +    qtest_writel(qts, R(HTX), 1);
> +    qtest_writel(qts, R(THR), 'H');
> +    g_assert_cmphex(rd(qts, TFL), ==, 1);
> +    g_assert_cmphex(rd(qts, RFL), ==, 0);
> +
> +    qtest_writel(qts, R(HTX), 0);
> +    g_assert_cmphex(rd(qts, TFL), ==, 0);
> +    g_assert_cmphex(rd(qts, RFL), ==, 1);
> +    g_assert_cmphex(rd(qts, THR), ==, 'H');
> +
> +    /* SRR: RFR & UR */
> +    qtest_writel(qts, R(THR), 'Z');
> +    g_assert_cmphex(rd(qts, RFL), ==, 1);
> +    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, LSR_DR);
> +    qtest_writel(qts, R(SRR), SRR_RFR);
> +    g_assert_cmphex(rd(qts, RFL), ==, 0);
> +    g_assert_cmphex(rd(qts, LSR) & LSR_DR, ==, 0);
> +    g_assert_cmphex(rd(qts, SRR), ==, 0);
> +    qtest_writel(qts, R(THR), 'Y');
> +    qtest_writel(qts, R(SRR), SRR_UR);
> +    g_assert_cmphex(rd(qts, LSR), ==, LSR_RESET);
> +    g_assert_cmphex(iid(qts), ==, IIR_NONE);
> +    g_assert_cmphex(rd(qts, RFL), ==, 0);
> +
> +    /* PTIME + TET programmable THRE */
> +    qtest_writel(qts, R(LCR), LCR_8N1);
> +    qtest_writel(qts, R(IIR), FCR_FE | FCR_TET_H);
> +    qtest_writel(qts, R(IER), IER_TX | IER_PTIME);
> +    qtest_writel(qts, R(THR), 'A');
> +    g_assert_cmphex(rd(qts, LSR) & LSR_THRE, ==, LSR_THRE);
> +    g_assert_cmphex(iid(qts), ==, IIR_THR);
> +
> +    qtest_quit(qts);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +    g_test_init(&argc, &argv, NULL);
> +    qtest_add_func("/k230-uart/device_probe",       test_device_probe);
> +    qtest_add_func("/k230-uart/init_and_baud",      test_init_and_baud);
> +    qtest_add_func("/k230-uart/tx_rx_datapath",     test_tx_rx_datapath);
> +    qtest_add_func("/k230-uart/thre_interrupt",     test_thre_interrupt);
> +    qtest_add_func("/k230-uart/rx_interrupts",      test_rx_interrupts);
> +    qtest_add_func("/k230-uart/error_interrupts",   test_error_interrupts);
> +    qtest_add_func("/k230-uart/busy_detect",        test_busy_detect);
> +    qtest_add_func("/k230-uart/advanced_features",  test_advanced_features);
> +    return g_test_run();
> +}
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 822e0bd286970339fe28127649feb131ce8a81b2..93246d9bf77206e9cebb9d8ade5801f4541d3b8d 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -294,7 +294,7 @@ qtests_riscv64 = ['riscv-csr-test'] + \
>     (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
>      config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
>      ['iommu-riscv-test'] : []) + \
> -  (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
> +  (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test', 'k230-uart-test'] : [])
>   
>   qtests_hexagon = ['boot-serial-test']
>   
> 



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH RESEND v2 1/3] hw/char: add K230 DW 8250-compatible UART
  2026-08-05 20:01   ` Daniel Henrique Barboza
@ 2026-08-07  9:49     ` zhenbaii
  0 siblings, 0 replies; 9+ messages in thread
From: zhenbaii @ 2026-08-07  9:49 UTC (permalink / raw)
  To: Daniel Henrique Barboza, qemu-devel
  Cc: Paolo Bonzini, Marc-André Lureau, Chao Liu, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Liu Zhiwei, qemu-riscv,
	Fabiano Rosas, Laurent Vivier


Hi Daniel,

Thanks for the review!


On 8/6/26 04:01, Daniel Henrique Barboza wrote:
> I believe a helper like 'static bool k230_uart_receiver_status_int' that
> encapsulates it is more idiomatic. 


Agreed. I'll make it into a helper in v3.


> If I have to make a guess I think these macros were being used as easy 
> knobs to test
> the emulation, i.e. manually enabling/disabling things in the header 
> file, rebuilding
> and see if the behaves as expected.  Which is of course fine, but 
> unless we're
> willing to make these knobs available to users (either by making these 
> available during
> ./configure or create device properties we can set in the command 
> line) I think we
> shouldn't use them in the logic since they're all hardwired.
>
> This is more pronounced down there in k230_uart_read().  We have this:
>
> > +            /* DLL accessible only when not busy. */
> > +            ret = (K230_UART_16550_COMPATIBLE || 
> !k230_uart_is_busy(s))
> > +                  ? s->dll : 0;
>
> K230_UART_16550_COMPATIBLE is hardwired to 0, hence this is equal to:
>
> ret = !k230_uart_is_busy(s) ? s->dll : 0;
>
> I believe we have more instances where we have a hardwired macro being 
> used
> as conditionals in the logic.  We should eliminate all of them to make 
> the
> logic simpler.


Yes as you say I use them for testing the emulation. Indeed it shouldn't 
be left in the logic. I will remove them in v3.


> These are all hardwired thus I recommend creating a macro in the 
> header like
> #define K230_UART_R_CPR (....)
> that encapsulates this value. 


Agreed. I will fix add K230_UART_R_CPR and the UCV macro in v3.


Regards,

WX Chen




^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH RESEND v2 3/3] tests/qtest: add K230 UART test
  2026-08-05 20:10   ` Daniel Henrique Barboza
@ 2026-08-07 11:53     ` WX Chen
  0 siblings, 0 replies; 9+ messages in thread
From: WX Chen @ 2026-08-07 11:53 UTC (permalink / raw)
  To: Daniel Henrique Barboza, qemu-devel
  Cc: Paolo Bonzini, Marc-André Lureau, Chao Liu, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Liu Zhiwei, qemu-riscv,
	Fabiano Rosas, Laurent Vivier


Hi Daniel,

Thanks for the review!


On 8/6/26 04:10, Daniel Henrique Barboza wrote:
>
> Any reason to use g_usleep() instead of qtest_clock_step()?  There are 
> a couple
> of g_usleep() uses in the code but you used lots of qtest_clock_step() 
> too.
>
> AFAIK g_usleep() is wall time and qtest_clock_step() is simulated 
> time, and
> I guess you should be using qtest_clock_step() for all the pollings 
> you're
> doing in this file.
>

You're right, there's no reason to mix them here. I'll fix this in v3.


Regards,

WX Chen




^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-08-07 11:53 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-25  3:52 [PATCH RESEND v2 0/3] riscv: Add K230 DW 8250-compatible UART WX Chen
2026-07-25  3:52 ` [PATCH RESEND v2 1/3] hw/char: add " WX Chen
2026-08-05 20:01   ` Daniel Henrique Barboza
2026-08-07  9:49     ` zhenbaii
2026-07-25  3:52 ` [PATCH RESEND v2 2/3] hw/riscv: k230: connect DW 8250 UART WX Chen
2026-08-05 20:02   ` Daniel Henrique Barboza
2026-07-25  3:52 ` [PATCH RESEND v2 3/3] tests/qtest: add K230 UART test WX Chen
2026-08-05 20:10   ` Daniel Henrique Barboza
2026-08-07 11:53     ` WX Chen

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.