From: Jack Wang <[email protected]>

Model the K230 Reset Management Unit (RMU / SYSCTL_RST) at 0x91101000.
Its control registers gather the software-controllable reset lines of the
SoC peripherals. The register semantics are modelled after the five reset
types described by the Linux mainline driver drivers/reset/reset-k230.c:

  - CPU0/CPU1: high-half per-bit write-enable strobe plus a done bit (bit12)
  - FLUSH:     the CPU register bit4, auto-cleared by hardware, no done bit
  - HW_DONE:   no write-enable; a done bit is latched when the reset fires
               and acknowledged by software with write-1-to-clear
  - SW_DONE:   plain read/write storage, no write-enable and no done bit

QEMU does not reset the target peripherals, so the model collapses the
hardware reset latency to zero: writing a reset request bit latches the
paired done bit and auto-clears the request bit in the same access, which
lets the kernel's readl_poll_timeout() loops succeed on the first read.

A qtest exercises the reset defaults, write-enable gating, HW_DONE latch
and write-1-to-clear, repeated resets, flush auto-clear and SW_DONE
storage behaviour.

Signed-off-by: Jack Wang <[email protected]>
---
 hw/misc/Kconfig             |   3 +
 hw/misc/k230_rmu.c          | 280 ++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build         |   1 +
 hw/misc/trace-events        |   6 +
 include/hw/misc/k230_rmu.h  |  76 ++++++++++
 tests/qtest/k230-rmu-test.c | 125 ++++++++++++++++
 tests/qtest/meson.build     |   3 +-
 7 files changed, 493 insertions(+), 1 deletion(-)
 create mode 100644 hw/misc/k230_rmu.c
 create mode 100644 include/hw/misc/k230_rmu.h
 create mode 100644 tests/qtest/k230-rmu-test.c

diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6653..a1470b285f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -251,6 +251,9 @@ config DJMEMC
 config IOSB
     bool
 
+config K230_RMU
+    bool
+
 config XLNX_VERSAL_TRNG
     bool
 
diff --git a/hw/misc/k230_rmu.c b/hw/misc/k230_rmu.c
new file mode 100644
index 0000000000..f9ead7a1a0
--- /dev/null
+++ b/hw/misc/k230_rmu.c
@@ -0,0 +1,280 @@
+/*
+ * K230 Reset Management Unit (RMU / SYSCTL_RST)
+ *
+ * The RMU is a bank of reset-control registers at 0x91101000. Each register
+ * gathers the software-controllable reset lines of a group of peripherals.
+ * Register semantics are modelled after the five reset types described in the
+ * Linux mainline driver drivers/reset/reset-k230.c:
+ *
+ *   - CPU0/CPU1 : have a high-half write-enable strobe and a done bit (bit12).
+ *   - FLUSH     : bit4 of the CPU registers; hardware auto-clears it, no done.
+ *   - HW_DONE   : no write-enable; a done bit is latched when the reset fires
+ *                 and cleared by software (write-1-to-clear).
+ *   - SW_DONE   : plain read/write storage, no write-enable and no done bit.
+ *
+ * QEMU never actually resets the target peripherals, so the model collapses
+ * the hardware reset latency to zero: writing a reset request bit latches the
+ * matching done bit in the same access and auto-clears the request bit, which
+ * lets the kernel's readl_poll_timeout() loops succeed on the first read.
+ *
+ * Copyright (c) 2026 Jack Wang <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "migration/vmstate.h"
+#include "hw/misc/k230_rmu.h"
+#include "trace.h"
+
+/* Sentinel meaning "this reset request bit has no associated done bit". */
+#define K230_RMU_NO_DONE  0xff
+
+/* A single (reset request bit -> done bit) pairing within one register. */
+typedef struct {
+    uint8_t reset_bit;   /* reset request bit index, 0..15               */
+    uint8_t done_bit;    /* matching done bit index, or K230_RMU_NO_DONE  */
+} K230RmuPair;
+
+/* Description of one control register. */
+typedef struct {
+    hwaddr             offset;   /* word-aligned register offset               
*/
+    bool               has_we;   /* high 16 bits are write-enable (CPU0/CPU1)  
*/
+    const K230RmuPair *pairs;    /* reset/done pairings, NULL for SW_DONE 
banks */
+    unsigned           n_pairs;  /* number of pairings (0 for SW_DONE banks)   
*/
+} K230RmuReg;
+
+/*
+ * Per-register (reset bit -> done bit) tables, copied verbatim from the bit
+ * positions in the Linux driver's k230_resets[] table.
+ */
+static const K230RmuPair pairs_cpu0[]    = { {0, 12}, {4, K230_RMU_NO_DONE} };
+static const K230RmuPair pairs_cpu1[]    = { {0, 12}, {4, K230_RMU_NO_DONE} };
+static const K230RmuPair pairs_ai[]      = { {0, 31} };
+static const K230RmuPair pairs_vpu[]     = { {0, 31} };
+static const K230RmuPair pairs_hisys[]   = { {0, 4}, {1, 5} };  /* done in low 
bits */
+static const K230RmuPair pairs_sdio[]    = { {0, 28}, {1, 29}, {2, 30} };
+static const K230RmuPair pairs_usb[]     = { {0, 28}, {1, 29}, {0, 30}, {1, 
31} };
+static const K230RmuPair pairs_spi[]     = { {0, 28}, {1, 29}, {2, 30} };
+static const K230RmuPair pairs_sec[]     = { {0, 31} };
+static const K230RmuPair pairs_dma[]     = { {0, 28}, {1, 29} };
+static const K230RmuPair pairs_decomp[]  = { {0, 31} };
+static const K230RmuPair pairs_sram[]    = { {0, 28}, {2, 30}, {3, 31} }; /* 
bit1 SW_DONE */
+static const K230RmuPair pairs_nonai2d[] = { {0, 31} };
+static const K230RmuPair pairs_mctl[]    = { {0, 31} };
+static const K230RmuPair pairs_isp[]     = { {6, 29}, {5, 28} }; /* other low 
bits SW_DONE */
+static const K230RmuPair pairs_dpu[]     = { {0, 31} };
+static const K230RmuPair pairs_disp[]    = { {0, 31} };
+static const K230RmuPair pairs_gpu[]     = { {0, 31} };
+static const K230RmuPair pairs_audio[]   = { {0, 31} };
+
+#define K230_RMU_REG(off, we, p)  { (off), (we), (p), ARRAY_SIZE(p) }
+#define K230_RMU_SW(off)          { (off), false, NULL, 0 }  /* pure SW_DONE 
bank */
+
+static const K230RmuReg k230_rmu_regs[] = {
+    K230_RMU_REG(K230_RMU_CPU0_CTRL,    true,  pairs_cpu0),
+    K230_RMU_REG(K230_RMU_CPU1_CTRL,    true,  pairs_cpu1),
+    K230_RMU_REG(K230_RMU_AI_CTRL,      false, pairs_ai),
+    K230_RMU_REG(K230_RMU_VPU_CTRL,     false, pairs_vpu),
+    K230_RMU_SW (K230_RMU_PERI0_CTRL),
+    K230_RMU_SW (K230_RMU_PERI1_CTRL),
+    K230_RMU_REG(K230_RMU_HISYS_CTRL,   false, pairs_hisys),
+    K230_RMU_REG(K230_RMU_SDIO_CTRL,    false, pairs_sdio),
+    K230_RMU_REG(K230_RMU_USB_CTRL,     false, pairs_usb),
+    K230_RMU_REG(K230_RMU_SPI_CTRL,     false, pairs_spi),
+    K230_RMU_REG(K230_RMU_SEC_CTRL,     false, pairs_sec),
+    K230_RMU_REG(K230_RMU_DMA_CTRL,     false, pairs_dma),
+    K230_RMU_REG(K230_RMU_DECOMP_CTRL,  false, pairs_decomp),
+    K230_RMU_REG(K230_RMU_SRAM_CTRL,    false, pairs_sram),
+    K230_RMU_REG(K230_RMU_NONAI2D_CTRL, false, pairs_nonai2d),
+    K230_RMU_REG(K230_RMU_MCTL_CTRL,    false, pairs_mctl),
+    K230_RMU_REG(K230_RMU_ISP_CTRL,     false, pairs_isp),
+    K230_RMU_REG(K230_RMU_DPU_CTRL,     false, pairs_dpu),
+    K230_RMU_REG(K230_RMU_DISP_CTRL,    false, pairs_disp),
+    K230_RMU_REG(K230_RMU_GPU_CTRL,     false, pairs_gpu),
+    K230_RMU_REG(K230_RMU_AUDIO_CTRL,   false, pairs_audio),
+    K230_RMU_SW (K230_RMU_SPI2AXI_CTRL),
+};
+
+/* Look up the register description for a word-aligned offset, or NULL. */
+static const K230RmuReg *k230_rmu_lookup(hwaddr offset)
+{
+    for (size_t i = 0; i < ARRAY_SIZE(k230_rmu_regs); i++) {
+        if (k230_rmu_regs[i].offset == offset) {
+            return &k230_rmu_regs[i];
+        }
+    }
+    return NULL;
+}
+
+static uint64_t k230_rmu_read(void *opaque, hwaddr offset, unsigned size)
+{
+    K230RmuState *s = K230_RMU(opaque);
+    uint32_t value;
+
+    if ((offset & 0x3) || offset >= K230_RMU_MMIO_SIZE) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+        return 0;
+    }
+
+    /* All hardware side effects happen on write, so a read just returns the
+     * backing store: done bits already latched, request bits already cleared.
+     */
+    value = s->regs[offset / 4];
+    trace_k230_rmu_read(offset, value);
+    return value;
+}
+
+static void k230_rmu_write(void *opaque, hwaddr offset,
+                           uint64_t val64, unsigned size)
+{
+    K230RmuState *s = K230_RMU(opaque);
+    const K230RmuReg *r;
+    uint32_t v = (uint32_t)val64;
+    uint32_t old, new_val, we_gate, done_gate;
+    uint32_t reset_mask = 0, done_mask = 0, sw_mask;
+
+    if ((offset & 0x3) || offset >= K230_RMU_MMIO_SIZE) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+        return;
+    }
+
+    trace_k230_rmu_write(offset, v);
+    old = s->regs[offset / 4];
+    r = k230_rmu_lookup(offset);
+
+    if (!r) {
+        /* Inside the 4 KiB window but not modelled: accept the write and log
+         * it so unexpected accesses are visible while debugging.
+         */
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: write to unmodelled offset 0x%" HWADDR_PRIx
+                      " = 0x%08x\n", __func__, offset, v);
+        s->regs[offset / 4] = v;
+        return;
+    }
+
+    /* Derive the reset/done masks for this register from its pairing table. */
+    for (unsigned i = 0; i < r->n_pairs; i++) {
+        reset_mask |= BIT(r->pairs[i].reset_bit);
+        if (r->pairs[i].done_bit != K230_RMU_NO_DONE) {
+            done_mask |= BIT(r->pairs[i].done_bit);
+        }
+    }
+
+    /*
+     * we_gate holds the low-half bits this write is allowed to modify. With
+     * write-enable (CPU0/CPU1) a low bit n is writable only when the strobe
+     * bit n+16 is also set; otherwise every low bit is directly writable.
+     */
+    we_gate = r->has_we ? ((v >> K230_RMU_WE_SHIFT) & 0xffffu) : 0xffffu;
+
+    new_val = old;
+
+    /* (A) SW_DONE storage: low-half bits that are neither reset nor done bits
+     *     are plain read/write storage.
+     */
+    sw_mask = 0xffffu & ~reset_mask & ~done_mask & we_gate;
+    new_val = (new_val & ~sw_mask) | (v & sw_mask);
+
+    /* (B) Done bits are write-1-to-clear. CPU-type done bits sit in the low
+     *     half and their clear is gated by write-enable (the strobe is folded
+     *     into we_gate); HW_DONE done bits sit in the high half, clear direct.
+     */
+    done_gate = r->has_we ? (done_mask & we_gate) : done_mask;
+    new_val &= ~(v & done_gate);
+
+    /* (C) A reset request latches its paired done bit immediately (zero
+     *     latency) and the request bit auto-clears so it can fire again.
+     *     FLUSH-type pairs (done_bit == NO_DONE) only auto-clear.
+     */
+    for (unsigned i = 0; i < r->n_pairs; i++) {
+        uint32_t rbit = BIT(r->pairs[i].reset_bit);
+        bool fire = (v & rbit) &&
+                    (!r->has_we || (v & (rbit << K230_RMU_WE_SHIFT)));
+
+        if (fire) {
+            if (r->pairs[i].done_bit != K230_RMU_NO_DONE) {
+                new_val |= BIT(r->pairs[i].done_bit);
+            } else {
+                trace_k230_rmu_flush(offset);
+            }
+            new_val &= ~rbit;
+        }
+    }
+
+    s->regs[offset / 4] = new_val;
+}
+
+static const MemoryRegionOps k230_rmu_ops = {
+    .read       = k230_rmu_read,
+    .write      = k230_rmu_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned       = false,
+    },
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void k230_rmu_reset(DeviceState *dev)
+{
+    K230RmuState *s = K230_RMU(dev);
+
+    trace_k230_rmu_reset_device();
+    memset(s->regs, 0, sizeof(s->regs));
+}
+
+static void k230_rmu_realize(DeviceState *dev, Error **errp)
+{
+    K230RmuState *s = K230_RMU(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    memory_region_init_io(&s->mmio, OBJECT(dev), &k230_rmu_ops, s,
+                          TYPE_K230_RMU, K230_RMU_MMIO_SIZE);
+    sysbus_init_mmio(sbd, &s->mmio);
+}
+
+static const VMStateDescription vmstate_k230_rmu = {
+    .name               = "k230.rmu",
+    .version_id         = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, K230RmuState, K230_RMU_NUM_REGS),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void k230_rmu_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = k230_rmu_realize;
+    device_class_set_legacy_reset(dc, k230_rmu_reset);
+    dc->vmsd = &vmstate_k230_rmu;
+    dc->desc = "K230 Reset Management Unit";
+}
+
+static const TypeInfo k230_rmu_info = {
+    .name          = TYPE_K230_RMU,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(K230RmuState),
+    .class_init    = k230_rmu_class_init,
+};
+
+static void k230_rmu_register_type(void)
+{
+    type_register_static(&k230_rmu_info);
+}
+type_init(k230_rmu_register_type)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6035..5e1155723f 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -111,6 +111,7 @@ system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files(
 system_ss.add(when: 'CONFIG_XLNX_VERSAL_TRNG', if_true: files(
   'xlnx-versal-trng.c',
 ))
+system_ss.add(when: 'CONFIG_K230_RMU', if_true: files('k230_rmu.c'))
 system_ss.add(when: 'CONFIG_STM32_RCC', if_true: files('stm32_rcc.c'))
 system_ss.add(when: 'CONFIG_STM32F2XX_SYSCFG', if_true: 
files('stm32f2xx_syscfg.c'))
 system_ss.add(when: 'CONFIG_STM32F4XX_SYSCFG', if_true: 
files('stm32f4xx_syscfg.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index b88accc437..da0e32582e 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -417,3 +417,9 @@ iommu_testdev_dma_read(uint64_t gva, uint32_t len) 
"gva=0x%" PRIx64 " len=%u"
 iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x 
actual=0x%x"
 iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
 iommu_testdev_dma_armed(bool armed) "armed=%d"
+
+# k230_rmu.c
+k230_rmu_read(uint64_t offset, uint32_t value) "K230 RMU read:  [0x%" PRIx64 
"] -> 0x%08" PRIx32
+k230_rmu_write(uint64_t offset, uint32_t value) "K230 RMU write: [0x%" PRIx64 
"] <- 0x%08" PRIx32
+k230_rmu_flush(uint64_t offset) "K230 RMU flush auto-clear at offset 0x%" 
PRIx64
+k230_rmu_reset_device(void) "K230 RMU device reset"
diff --git a/include/hw/misc/k230_rmu.h b/include/hw/misc/k230_rmu.h
new file mode 100644
index 0000000000..e6f5073c1e
--- /dev/null
+++ b/include/hw/misc/k230_rmu.h
@@ -0,0 +1,76 @@
+/*
+ * K230 Reset Management Unit (RMU / SYSCTL_RST)
+ *
+ * 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 Linux mainline driver
+ * drivers/reset/reset-k230.c (compatible "canaan,k230-rst").
+ *
+ * Copyright (c) 2026 Jack Wang <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_K230_RMU_H
+#define HW_MISC_K230_RMU_H
+
+#include "qemu/bitops.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_K230_RMU "riscv.k230.rmu"
+OBJECT_DECLARE_SIMPLE_TYPE(K230RmuState, K230_RMU)
+
+/* 1 KiB MMIO window, see K230_DEV_RMU in hw/riscv/k230.c. */
+#define K230_RMU_MMIO_SIZE   0x1000
+#define K230_RMU_NUM_REGS    (K230_RMU_MMIO_SIZE / 4)
+
+/* Control register offsets used by drivers/reset/reset-k230.c. */
+#define K230_RMU_CPU0_CTRL     0x04
+#define K230_RMU_CPU1_CTRL     0x0C
+#define K230_RMU_AI_CTRL       0x14
+#define K230_RMU_VPU_CTRL      0x1C
+#define K230_RMU_PERI0_CTRL    0x20   /* SW_DONE bank: TIMERx, WDTx, MAILBOX   
*/
+#define K230_RMU_PERI1_CTRL    0x24   /* SW_DONE bank: UARTx, I2Cx, GPIO       
*/
+#define K230_RMU_HISYS_CTRL    0x2C
+#define K230_RMU_SDIO_CTRL     0x34
+#define K230_RMU_USB_CTRL      0x3C
+#define K230_RMU_SPI_CTRL      0x44
+#define K230_RMU_SEC_CTRL      0x4C
+#define K230_RMU_DMA_CTRL      0x54
+#define K230_RMU_DECOMP_CTRL   0x5C
+#define K230_RMU_SRAM_CTRL     0x64   /* mixed: HW_DONE bits + SW_DONE bit1    
 */
+#define K230_RMU_NONAI2D_CTRL  0x6C
+#define K230_RMU_MCTL_CTRL     0x74
+#define K230_RMU_ISP_CTRL      0x80   /* mixed: HW_DONE bits + SW_DONE bits    
 */
+#define K230_RMU_DPU_CTRL      0x88
+#define K230_RMU_DISP_CTRL     0x90
+#define K230_RMU_GPU_CTRL      0x98
+#define K230_RMU_AUDIO_CTRL    0xA4
+#define K230_RMU_SPI2AXI_CTRL  0xA8   /* SW_DONE (active high)                 
 */
+
+/* Bit layout shared by the CPU0/CPU1 control registers. */
+#define K230_RMU_CPU_RESET     BIT(0)    /* reset request, auto/soft cleared   
 */
+#define K230_RMU_CPU_FLUSH     BIT(4)    /* L2 flush request, hw auto-clears   
  */
+#define K230_RMU_CPU_DONE      BIT(12)   /* done bit, write-1-to-clear         
  */
+
+/* The high 16 bits are per-bit write-enable strobes (CPU0/CPU1 registers). */
+#define K230_RMU_WE_SHIFT      16
+
+struct K230RmuState {
+    /*< private >*/
+    SysBusDevice parent_obj;
+
+    /*< public >*/
+    MemoryRegion mmio;
+
+    /*
+     * One 32-bit word per MMIO offset. Indexing by word offset keeps the
+     * VMState description trivial. Reset requests auto-clear, so what
+     * actually persists here is done bits and SW_DONE storage bits.
+     */
+    uint32_t regs[K230_RMU_NUM_REGS];
+};
+
+#endif /* HW_MISC_K230_RMU_H */
diff --git a/tests/qtest/k230-rmu-test.c b/tests/qtest/k230-rmu-test.c
new file mode 100644
index 0000000000..c2c862cd1d
--- /dev/null
+++ b/tests/qtest/k230-rmu-test.c
@@ -0,0 +1,125 @@
+/*
+ * QTest for the K230 Reset Management Unit.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "libqtest.h"
+#include "hw/misc/k230_rmu.h"
+
+#define K230_RMU_BASE 0x91101000ULL
+
+static inline uint32_t rd(QTestState *qts, uint64_t off)
+{
+    return qtest_readl(qts, K230_RMU_BASE + off);
+}
+static inline void wr(QTestState *qts, uint64_t off, uint32_t val)
+{
+    qtest_writel(qts, K230_RMU_BASE + off, val);
+}
+
+/* Every known register reads back as 0 after reset. */
+static void test_reset_values(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    g_assert_cmphex(rd(qts, K230_RMU_CPU0_CTRL),  ==, 0);
+    g_assert_cmphex(rd(qts, K230_RMU_AI_CTRL),    ==, 0);
+    g_assert_cmphex(rd(qts, K230_RMU_HISYS_CTRL), ==, 0);
+    g_assert_cmphex(rd(qts, K230_RMU_PERI0_CTRL), ==, 0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * CPU0: a low-half write without its strobe is ignored; with the strobe the
+ * reset request fires, latches done (bit12), and the request bit auto-clears.
+ */
+static void test_cpu_write_enable(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    /* No strobe: the low-half write is dropped, reads back as 0. */
+    wr(qts, K230_RMU_CPU0_CTRL, K230_RMU_CPU_RESET);
+    g_assert_cmphex(rd(qts, K230_RMU_CPU0_CTRL), ==, 0);
+
+    /* With strobe (bit16): request bit0 takes effect, done (bit12) latches. */
+    wr(qts, K230_RMU_CPU0_CTRL,
+       K230_RMU_CPU_RESET | (K230_RMU_CPU_RESET << K230_RMU_WE_SHIFT));
+    g_assert_cmphex(rd(qts, K230_RMU_CPU0_CTRL), ==, K230_RMU_CPU_DONE);
+
+    qtest_quit(qts);
+}
+
+/*
+ * HW_DONE: no write-enable needed. Writing HISYS request bit0 latches its
+ * done bit (bit4); writing 1 to the done bit clears it.
+ */
+static void test_hw_done_and_w1c(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+    uint32_t v;
+
+    wr(qts, K230_RMU_HISYS_CTRL, BIT(0));      /* no strobe */
+    v = rd(qts, K230_RMU_HISYS_CTRL);
+    g_assert_cmphex(v & BIT(4), ==, BIT(4));   /* done latched */
+    g_assert_cmphex(v & BIT(0), ==, 0);        /* request bit auto-cleared */
+
+    wr(qts, K230_RMU_HISYS_CTRL, BIT(4));      /* write-1-to-clear */
+    g_assert_cmphex(rd(qts, K230_RMU_HISYS_CTRL) & BIT(4), ==, 0);
+
+    qtest_quit(qts);
+}
+
+/* Repeated resets: one HW_DONE line can be triggered again and again. */
+static void test_repeat_reset(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    for (int i = 0; i < 3; i++) {
+        wr(qts, K230_RMU_AI_CTRL, BIT(0));                 /* request */
+        g_assert_cmphex(rd(qts, K230_RMU_AI_CTRL) & BIT(31), ==, BIT(31));
+        wr(qts, K230_RMU_AI_CTRL, BIT(31));                /* W1C done */
+        g_assert_cmphex(rd(qts, K230_RMU_AI_CTRL) & BIT(31), ==, 0);
+    }
+
+    qtest_quit(qts);
+}
+
+/* FLUSH: the CPU0 bit4 flush request is auto-cleared, reads back as 0. */
+static void test_flush_auto_clear(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    wr(qts, K230_RMU_CPU0_CTRL,
+       K230_RMU_CPU_FLUSH | (K230_RMU_CPU_FLUSH << K230_RMU_WE_SHIFT));
+    g_assert_cmphex(rd(qts, K230_RMU_CPU0_CTRL) & K230_RMU_CPU_FLUSH, ==, 0);
+
+    qtest_quit(qts);
+}
+
+/* SW_DONE: the PERI0 low half is plain storage, no strobe and no done bit. */
+static void test_sw_done_storage(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    wr(qts, K230_RMU_PERI0_CTRL, BIT(12) | BIT(13));   /* WDT0 / WDT1 reset 
bits */
+    g_assert_cmphex(rd(qts, K230_RMU_PERI0_CTRL), ==, (BIT(12) | BIT(13)));
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/k230/rmu/reset_values",   test_reset_values);
+    qtest_add_func("/k230/rmu/cpu_we",         test_cpu_write_enable);
+    qtest_add_func("/k230/rmu/hw_done_w1c",    test_hw_done_and_w1c);
+    qtest_add_func("/k230/rmu/repeat_reset",   test_repeat_reset);
+    qtest_add_func("/k230/rmu/flush",          test_flush_auto_clear);
+    qtest_add_func("/k230/rmu/sw_done",        test_sw_done_storage);
+
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 822e0bd286..70c9a53454 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -294,7 +294,8 @@ 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-rmu-test'] : [])
 
 qtests_hexagon = ['boot-serial-test']
 
-- 
2.53.0


Reply via email to