* [RFC PATCH 0/2] *** Add k230 reset management unit support ***
@ 2026-07-09 3:12 jack wang
2026-07-09 3:12 ` [RFC PATCH 1/2] hw/misc/k230_rmu: add Kendryte K230 Reset Management Unit model jack wang
2026-07-09 3:12 ` [RFC PATCH 2/2] hw/riscv/k230: wire up the RMU device jack wang
0 siblings, 2 replies; 3+ messages in thread
From: jack wang @ 2026-07-09 3:12 UTC (permalink / raw)
To: qemu-devel; +Cc: Jack Wang
From: Jack Wang <163wangjack@gmail.com>
This series adds support for reset management unit found on k230 soc and
successfully wires it up to k230 machine;
The register design are implemented based on k230 trm and linux reset driver
(drivers/reset/reset-k230.c)
detailed implementations:
1. write-enable mask for control registers(strobe in the upper 16 bits)
2. hardware auto-clearing for reset and flush request bits
3. latching of Done bits and w1c logic
4. qtest coverage for register read/write behaviors and hardware state modelin
There are some checkpatch style warnings (line length, comment
formatting) that will be addressed in v2 based on reviewer feedback.
this is my first time for hardware-unit modeling so apologies for possible mistake.
I am apprecitated for any feedback
Jack Wang (2):
hw/misc/k230_rmu: add Kendryte K230 Reset Management Unit model
hw/riscv/k230: wire up the RMU device
docs/system/riscv/k230.rst | 1 +
hw/misc/Kconfig | 3 +
hw/misc/k230_rmu.c | 280 ++++++++++++++++++++++++++++++++++++
hw/misc/meson.build | 1 +
hw/misc/trace-events | 6 +
hw/riscv/Kconfig | 1 +
hw/riscv/k230.c | 10 +-
include/hw/misc/k230_rmu.h | 76 ++++++++++
include/hw/riscv/k230.h | 2 +
tests/qtest/k230-rmu-test.c | 125 ++++++++++++++++
tests/qtest/meson.build | 3 +-
11 files changed, 504 insertions(+), 4 deletions(-)
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
--
2.53.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [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
* [RFC PATCH 2/2] hw/riscv/k230: wire up the RMU device
2026-07-09 3:12 [RFC PATCH 0/2] *** Add k230 reset management unit support *** jack wang
2026-07-09 3:12 ` [RFC PATCH 1/2] hw/misc/k230_rmu: add Kendryte K230 Reset Management Unit model jack wang
@ 2026-07-09 3:12 ` 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, Chao Liu, Pierrick Bouvier, Paolo Bonzini,
Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, open list:K230 Machines
From: Jack Wang <163wangjack@gmail.com>
Replace the "rmu" create_unimplemented_device() stub with a real
K230RmuState instance mapped at the K230_DEV_RMU memmap entry, select
K230_RMU from the K230 Kconfig, and document the device.
Closes: gevico/qemu-camp-2026-k230#11
Signed-off-by: Jack Wang <163wangjack@gmail.com>
---
docs/system/riscv/k230.rst | 1 +
hw/riscv/Kconfig | 1 +
hw/riscv/k230.c | 10 +++++++---
include/hw/riscv/k230.h | 2 ++
4 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst
index cea8202e55..3f2313a127 100644
--- a/docs/system/riscv/k230.rst
+++ b/docs/system/riscv/k230.rst
@@ -19,6 +19,7 @@ The ``k230`` machine supports the following devices:
* Core Local Interruptor (CLINT)
* Platform-Level Interrupt Controller (PLIC)
* 2 K230 Watchdog Timer
+* K230 Reset Management Unit (RMU)
* 5 UART
Boot options
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 54e41a6afc..bffa4e69c8 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -149,3 +149,4 @@ config K230
select SERIAL_MM
select UNIMP
select K230_WDT
+ select K230_RMU
diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
index 502281c52c..b55e90c0d2 100644
--- a/hw/riscv/k230.c
+++ b/hw/riscv/k230.c
@@ -110,6 +110,7 @@ static void k230_soc_init(Object *obj)
object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY);
object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT);
object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT);
+ object_initialize_child(obj, "k230-rmu", &s->rmu, TYPE_K230_RMU);
qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
@@ -206,6 +207,12 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[1]), 0,
qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT1_IRQ));
+ /* RMU (reset management unit) */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->rmu), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->rmu), 0, memmap[K230_DEV_RMU].base);
+
/* unimplemented devices */
create_unimplemented_device("kpu.l2-cache",
memmap[K230_DEV_KPU_L2_CACHE].base,
@@ -268,9 +275,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
create_unimplemented_device("cmu", memmap[K230_DEV_CMU].base,
memmap[K230_DEV_CMU].size);
- create_unimplemented_device("rmu", memmap[K230_DEV_RMU].base,
- memmap[K230_DEV_RMU].size);
-
create_unimplemented_device("boot", memmap[K230_DEV_BOOT].base,
memmap[K230_DEV_BOOT].size);
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
index 592e1c26bf..13f2a1ab0b 100644
--- a/include/hw/riscv/k230.h
+++ b/include/hw/riscv/k230.h
@@ -18,6 +18,7 @@
#include "hw/core/boards.h"
#include "hw/riscv/riscv_hart.h"
#include "hw/watchdog/k230_wdt.h"
+#include "hw/misc/k230_rmu.h"
#define C908_CPU_HARTID (0)
@@ -33,6 +34,7 @@ typedef struct K230SoCState {
RISCVHartArrayState c908_cpu; /* Small core */
K230WdtState wdt[2];
+ K230RmuState rmu;
MemoryRegion sram;
MemoryRegion bootrom;
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-09 3:13 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-09 3:12 [RFC PATCH 0/2] *** Add k230 reset management unit support *** jack wang
2026-07-09 3:12 ` [RFC PATCH 1/2] hw/misc/k230_rmu: add Kendryte K230 Reset Management Unit model jack wang
2026-07-09 3:12 ` [RFC PATCH 2/2] hw/riscv/k230: wire up the RMU device jack wang
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.