Hi,

On 7/9/2026 12:12 AM, jack wang wrote:
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.


The qtest fails with this patch:

 29/352 qtest+qtest-riscv64 - qemu:qtest-riscv64/k230-rmu-test               
ERROR            0.33s   killed by signal 6 SIGABRT


Error being thrown:

# starting QEMU: exec ./qemu-system-riscv64 -qtest unix:/tmp/qtest-3816689.sock 
-qtest-log /dev/null -chardev socket,path=/tmp/qtest-3816689.qmp,id=char0 
-object monitor-qmp,id=qmp0,chardev=char0 -display none -audio none -run-with 
exit-with-parent=on -machine k230  -accel qtest
ok 1 /riscv64/k230/rmu/reset_values
# starting QEMU: exec ./qemu-system-riscv64 -qtest unix:/tmp/qtest-3816689.sock 
-qtest-log /dev/null -chardev socket,path=/tmp/qtest-3816689.qmp,id=char0 
-object monitor-qmp,id=qmp0,chardev=char0 -display none -audio none -run-with 
exit-with-parent=on -machine k230  -accel qtest
**
ERROR:../tests/qtest/k230-rmu-test.c:50:test_cpu_write_enable: assertion failed 
(rd(qts, K230_RMU_CPU0_CTRL) == K230_RMU_CPU_DONE): (0x00000000 == 0x00001000)
not ok /riscv64/k230/rmu/cpu_we - 
ERROR:../tests/qtest/k230-rmu-test.c:50:test_cpu_write_enable: assertion failed 
(rd(qts, K230_RMU_CPU0_CTRL) == K230_RMU_CPU_DONE): (0x00000000 == 0x00001000)
Bail out!


If we apply patch 2/2 the qtest succeeds.  Which is good, but we aim for all
tests being green for all individual patches to ease our lives when bisecting
code.

You can either squash patch 2/2 into this one or move the new qtest to patch 2/2
where it'll pass.


The code LGTM aside from this bit:


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


[...]

+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);


This is a deprecated API.  If you look at include/hw/core/qdev.h:


---
* device_class_set_legacy_reset(): set the DeviceClass::reset method
 * @dc: The device class
 * @dev_reset: the reset function
 *
 * This function sets the DeviceClass::reset method. This is widely
 * used in existing code, but new code should prefer to use the
 * Resettable API as documented in docs/devel/reset.rst.
---

I suggest reading docs/devel/reset.rst to understand which reset phases might
be applicable to this device.  Some devices implement just the phases.hold cb,
others implement enter and/or exit.  Seeing k230_rmu_reset() I think you can
get away with implementing it with just phases.hold but don't just take my word
for it.  Check out the docs and see what makes sense for this RMU.


Thanks,
Daniel


+    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']


Reply via email to