* [RFC PATCH 1/2] hw/misc/k230_rmu: add Kendryte K230 Reset Management Unit model
2026-07-09 3:12 [RFC PATCH 0/2] *** Add k230 reset management unit support *** jack wang
@ 2026-07-09 3:12 ` jack wang
2026-07-09 3:12 ` [RFC PATCH 2/2] hw/riscv/k230: wire up the RMU device jack wang
1 sibling, 0 replies; 3+ messages in thread
From: jack wang @ 2026-07-09 3:12 UTC (permalink / raw)
To: qemu-devel; +Cc: Jack Wang, Paolo Bonzini, Fabiano Rosas, Laurent Vivier
From: Jack Wang <163wangjack@gmail.com>
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 <163wangjack@gmail.com>
---
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 <163wangjack@gmail.com>
+ *
+ * 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 <163wangjack@gmail.com>
+ *
+ * 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
^ permalink raw reply related [flat|nested] 3+ messages in thread