[RFC PATCH 0/2] *** Add k230 reset management unit support ***

jack wang posted 2 patches 2 weeks, 3 days ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260709031237.21284-1-163wangjack@gmail.com
Maintainers: Chao Liu <chao.liu@processmission.com>, Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>, Paolo Bonzini <pbonzini@redhat.com>, Palmer Dabbelt <palmer@dabbelt.com>, Alistair Francis <alistair.francis@wdc.com>, Weiwei Li <liwei1518@gmail.com>, Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>, Liu Zhiwei <zhiwei_liu@linux.alibaba.com>, Fabiano Rosas <farosas@suse.de>, Laurent Vivier <lvivier@redhat.com>
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
[RFC PATCH 0/2] *** Add k230 reset management unit support ***
Posted by jack wang 2 weeks, 3 days ago
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
[PATCH v2 0/2] hw/riscv/k230: add the K230 Reset Management Unit
Posted by Jack Wang 6 days, 15 hours ago
References: <20260709031237.21284-1-163wangjack@gmail.com>

This v2 patch reply on review of 
This series models the Kendryte K230 Reset Management Unit (RMU), a bank of
reset-control registers at 0x91101000, and wires it into the K230 SoC. It
replaces the create_unimplemented_device("rmu") stub so guest reset drivers
(drivers/reset/reset-k230.c) work against a real model.

Patch 1 adds the standalone device model; patch 2 wires it into the SoC and
adds the qtest. The qtest is deliberately placed in patch 2 accorinding to 
suggestion from Daniel: it drives "-machine k230", which only
exists once the device is wired up.

Register semantics, reset values and reserved-bit masks are taken from the
K230 TRM chapter 2.1 "Reset" (V0.3.1) and cross-checked against the mainline
Linux driver.

Changes since v1:
 - Switched to the Resettable API (phases.hold instead of the legacy
   device_class_set_legacy_reset()), per Daniel's review.
 - Model real reset propagation: the RMU now cold-resets the two linked
   watchdogs via "wdt0"/"wdt1" QOM links when their PERI0 reset bits are
   written (Cao review).
 - CPU1 reset request is now a two-step assert/deassert (not self-clearing),
   matching the hardware (Cao review ).
 - Filled in the documented per-register reset values and reserved-bit
   (writable) masks from the TRM; the *_rst_done status bits reset to 0, so
   they follow the per-bit "Reset" column rather than the summarised total.
 - Added the reset-time-control (*_RST_TIM) registers as plain storage with
   their documented reset values.
 - Fixed the storage mask so the upper half of non-write-enable registers is
   writable (surfaced by the new TIM registers).
 - New qtests: reset values, TIM storage, reserved-bit read-only, CPU1
   two-step, and real watchdog reset propagation.
 - Moved the qtest from patch 1 to patch 2 for bisectability; added the
   MAINTAINERS entries.

Jack Wang (2):
  hw/misc/k230_rmu: add Kendryte K230 Reset Management Unit model
  hw/riscv/k230: wire up the RMU device

 MAINTAINERS                 |   3 +
 docs/system/riscv/k230.rst  |   1 +
 hw/misc/Kconfig             |   3 +
 hw/misc/k230_rmu.c          | 425 ++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build         |   1 +
 hw/misc/trace-events        |   7 +
 hw/riscv/Kconfig            |   1 +
 hw/riscv/k230.c             |  15 +-
 include/hw/misc/k230_rmu.h  | 126 +++++++++++
 include/hw/riscv/k230.h     |   2 +
 tests/qtest/k230-rmu-test.c | 225 +++++++++++++++++++
 tests/qtest/meson.build     |   3 +-
 12 files changed, 808 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
[PATCH v2 1/2] hw/misc/k230_rmu: add Kendryte K230 Reset Management Unit model
Posted by Jack Wang 6 days, 15 hours ago
The K230 Reset Management Unit (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 reset
types described in the Linux mainline driver drivers/reset/reset-k230.c and
cross-checked against the K230 TRM chapter 2.1 "Reset":

  - CPU0      : high-half write-enable strobe and a done bit (bit12); the
                reset request is self-clearing.
  - CPU1      : same layout as CPU0 but the reset request is NOT self-
                clearing: software asserts it and later deasserts it as two
                separate operations.
  - 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.
  - *_RST_TIM : reset-time-control registers; plain read/write storage that
                comes up with its documented reset value.

For most groups QEMU has no real peripheral to reset, 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 (for self-clearing lines) auto-
clears the request bit, which lets the kernel's readl_poll_timeout() loops
succeed on the first read. The reset values and reserved-bit masks of every
modelled register are taken from the K230 TRM chapter 2.1.5.

An optional pair of "wdt0"/"wdt1" QOM links lets the machine connect the two
watchdogs; asserting their PERI0 reset bits then performs a real
device_cold_reset() on the linked WDT devices.

This commit adds:
- Device model in hw/misc/k230_rmu.c with per-register pairing tables
- Header with register offsets and bit definitions
- Kconfig and meson.build integration
- Trace events for read/write/reset operations

Signed-off-by: Jack Wang <163wangjack@gmail.com>
---
 MAINTAINERS                |   2 +
 hw/misc/Kconfig            |   3 +
 hw/misc/k230_rmu.c         | 425 +++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build        |   1 +
 hw/misc/trace-events       |   7 +
 include/hw/misc/k230_rmu.h | 126 +++++++++++
 6 files changed, 564 insertions(+)
 create mode 100644 hw/misc/k230_rmu.c
 create mode 100644 include/hw/misc/k230_rmu.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 2ecfd7159d..80061ce66d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1808,8 +1808,10 @@ M: Chao Liu <chao.liu.zevorn@gmail.com>
 L: qemu-riscv@nongnu.org
 S: Maintained
 F: docs/system/riscv/k230.rst
+F: hw/misc/k230_rmu.c
 F: hw/riscv/k230.c
 F: hw/watchdog/k230_wdt.c
+F: include/hw/misc/k230_rmu.h
 F: include/hw/riscv/k230.h
 F: include/hw/watchdog/k230_wdt.h
 F: tests/qtest/k230-wdt-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..af9767b3f4
--- /dev/null
+++ b/hw/misc/k230_rmu.c
@@ -0,0 +1,425 @@
+/*
+ * 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      : high-half write-enable strobe, a done bit (bit12); the reset
+ *                 request is self-clearing.
+ *   - CPU1      : same layout as CPU0 but the reset request is NOT self-
+ *                 clearing: software must assert it and later deassert it as
+ *                 two separate operations.
+ *   - 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.
+ *
+ * For most groups QEMU has no real peripheral to reset, 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 (for self-clearing lines) auto-
+ * clears the request bit, which lets the kernel's readl_poll_timeout() loops
+ * succeed on the first read. For the watchdogs, which ARE modelled, asserting
+ * their PERI0 reset bits additionally performs a real device_cold_reset() on
+ * the linked WDT devices (still zero latency, guest-visible ordering intact).
+ *
+ * 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/core/qdev.h"
+#include "hw/core/qdev-properties.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  */
+    /*
+     * true:  the reset request auto-clears once the reset fires.
+     * false: it stays asserted until software deasserts it (CPU1).
+     */
+    bool    self_clear;
+} K230RmuPair;
+
+/* Description of one control register. */
+typedef struct {
+    hwaddr             offset;        /* word-aligned register offset */
+    bool               has_we;        /* high 16 bits are write-enable */
+    const K230RmuPair *pairs;         /* reset/done pairs, NULL for SW_DONE */
+    unsigned           n_pairs;       /* number of pairings (0 for SW_DONE) */
+    uint32_t           reset_val;     /* TRM reset value (per-bit column) */
+    uint32_t           writable_mask; /* writable bits; reserved bits are 0 */
+} K230RmuReg;
+
+/*
+ * Per-register (reset bit -> done bit) tables, copied verbatim from the bit
+ * positions in the Linux driver's k230_resets[] table. The last field marks
+ * whether the reset request auto-clears (see K230RmuPair::self_clear).
+ */
+static const K230RmuPair pairs_cpu0[]    = { {0, 12, true},
+                                             {4, K230_RMU_NO_DONE, true} };
+/* CPU1 reset bit0 is NOT self-clearing: assert and deassert are two steps. */
+static const K230RmuPair pairs_cpu1[]    = { {0, 12, false},
+                                             {4, K230_RMU_NO_DONE, true} };
+static const K230RmuPair pairs_ai[]      = { {0, 31, true} };
+static const K230RmuPair pairs_vpu[]     = { {0, 31, true} };
+/* done in low bits */
+static const K230RmuPair pairs_hisys[]   = { {0, 4, true}, {1, 5, true} };
+static const K230RmuPair pairs_sdio[]    = { {0, 28, true}, {1, 29, true},
+                                             {2, 30, true} };
+static const K230RmuPair pairs_usb[]     = { {0, 28, true}, {1, 29, true},
+                                             {0, 30, true}, {1, 31, true} };
+static const K230RmuPair pairs_spi[]     = { {0, 28, true}, {1, 29, true},
+                                             {2, 30, true} };
+static const K230RmuPair pairs_sec[]     = { {0, 31, true} };
+static const K230RmuPair pairs_dma[]     = { {0, 28, true}, {1, 29, true} };
+static const K230RmuPair pairs_decomp[]  = { {0, 31, true} };
+/* bit1 SW_DONE */
+static const K230RmuPair pairs_sram[]    = { {0, 28, true}, {2, 30, true},
+                                             {3, 31, true} };
+static const K230RmuPair pairs_nonai2d[] = { {0, 31, true} };
+static const K230RmuPair pairs_mctl[]    = { {0, 31, true} };
+/* other low bits SW_DONE */
+static const K230RmuPair pairs_isp[]     = { {6, 29, true}, {5, 28, true} };
+static const K230RmuPair pairs_dpu[]     = { {0, 31, true} };
+static const K230RmuPair pairs_disp[]    = { {0, 31, true} };
+static const K230RmuPair pairs_gpu[]     = { {0, 31, true} };
+static const K230RmuPair pairs_audio[]   = { {0, 31, true} };
+
+/*
+ * Reset values and reserved-bit masks are taken from the K230 TRM (chapter
+ * 2.1.5).
+ *
+ * reset_val uses the per-bit "Reset" column of each register (the status
+ * "*_rst_done" bits reset to 0, so it can differ from the register's summarised
+ * "Total Reset Value"). writable_mask lists the bits the TRM documents as
+ * accessible; undocumented (reserved) bits read back as 0 and ignore writes.
+ *
+ * K230_RMU_REG():  a control register with a reset/done pairing table.
+ * K230_RMU_SW():   a plain read/write storage bank (no reset/done pairs).
+ * K230_RMU_TIM():  a reset-time-control register - plain storage, fully
+ *                  writable, backed by its documented reset value.
+ */
+#define K230_RMU_REG(off, we, p, rst, wmask) \
+    { (off), (we), (p), ARRAY_SIZE(p), (rst), (wmask) }
+#define K230_RMU_SW(off, rst, wmask) \
+    { (off), false, NULL, 0, (rst), (wmask) }
+#define K230_RMU_TIM(off, rst) \
+    { (off), false, NULL, 0, (rst), 0xffffffffu }
+
+static const K230RmuReg k230_rmu_regs[] = {
+    /* Control registers (offset, write-enable, pairs, reset_val, writable). */
+    K230_RMU_REG(K230_RMU_CPU0_CTRL, true, pairs_cpu0, 0x00000000, 0x0000701f),
+    K230_RMU_REG(K230_RMU_CPU1_CTRL, true, pairs_cpu1, 0x00000001, 0x00003011),
+    K230_RMU_REG(K230_RMU_AI_CTRL, false, pairs_ai, 0x00000000, 0x80000001),
+    K230_RMU_REG(K230_RMU_VPU_CTRL, false, pairs_vpu, 0x00000000, 0x80000001),
+    K230_RMU_SW(K230_RMU_PERI0_CTRL, 0x000ff0ff, 0x000ff0ff),
+    K230_RMU_SW(K230_RMU_PERI1_CTRL, 0x007e7fff, 0x007e7fff),
+    K230_RMU_REG(K230_RMU_HISYS_CTRL, false, pairs_hisys, 0x00000000, 0x33),
+    K230_RMU_REG(K230_RMU_SDIO_CTRL, false, pairs_sdio, 0x00000000, 0x70000007),
+    K230_RMU_REG(K230_RMU_USB_CTRL, false, pairs_usb, 0x00000000, 0xf0000003),
+    K230_RMU_REG(K230_RMU_SPI_CTRL, false, pairs_spi, 0x00000000, 0x70000007),
+    K230_RMU_REG(K230_RMU_SEC_CTRL, false, pairs_sec, 0x00000000, 0x80000001),
+    K230_RMU_REG(K230_RMU_DMA_CTRL, false, pairs_dma, 0x00000000, 0x30000003),
+    K230_RMU_REG(K230_RMU_DECOMP_CTRL, false, pairs_decomp, 0x0, 0x80000001),
+    K230_RMU_REG(K230_RMU_SRAM_CTRL, false, pairs_sram, 0x00000002, 0xd000000f),
+    K230_RMU_REG(K230_RMU_NONAI2D_CTRL, false, pairs_nonai2d, 0x0, 0x80000001),
+    K230_RMU_REG(K230_RMU_MCTL_CTRL, false, pairs_mctl, 0x00000000, 0x80000001),
+    K230_RMU_REG(K230_RMU_ISP_CTRL, false, pairs_isp, 0x0000039f, 0x300003ff),
+    K230_RMU_REG(K230_RMU_DPU_CTRL, false, pairs_dpu, 0x00000000, 0x80000001),
+    K230_RMU_REG(K230_RMU_DISP_CTRL, false, pairs_disp, 0x00000000, 0x80000001),
+    K230_RMU_REG(K230_RMU_GPU_CTRL, false, pairs_gpu, 0x00000000, 0x80000001),
+    K230_RMU_REG(K230_RMU_AUDIO_CTRL, false, pairs_audio, 0x0, 0x80000001),
+    K230_RMU_SW(K230_RMU_SPI2AXI_CTRL, 0x00000000, 0xffffffff),
+
+    /* Reset-time-control registers (offset, reset_val). */
+    K230_RMU_TIM(K230_RMU_CPU0_TIM, 0x00fff880),
+    K230_RMU_TIM(K230_RMU_CPU1_TIM, 0x00066660),
+    K230_RMU_TIM(K230_RMU_AI_TIM, 0x00000880),
+    K230_RMU_TIM(K230_RMU_VPU_TIM, 0x00000880),
+    K230_RMU_TIM(K230_RMU_HISYS_TIM, 0x00080800),
+    K230_RMU_TIM(K230_RMU_SDCTL_TIM, 0x00080800),
+    K230_RMU_TIM(K230_RMU_USB_TIM, 0x0008d288),
+    K230_RMU_TIM(K230_RMU_SPI_TIM, 0x00080c00),
+    K230_RMU_TIM(K230_RMU_SEC_TIM, 0x00080800),
+    K230_RMU_TIM(K230_RMU_DMAC_TIM, 0x00040400),
+    K230_RMU_TIM(K230_RMU_DECOMP_TIM, 0x00040400),
+    K230_RMU_TIM(K230_RMU_SRAM_TIM, 0x00020200),
+    K230_RMU_TIM(K230_RMU_NONAI2D_TIM, 0x00020200),
+    K230_RMU_TIM(K230_RMU_MCTL_TIM, 0x00000304),
+    K230_RMU_TIM(K230_RMU_ISP_TIM, 0x00030202),
+    K230_RMU_TIM(K230_RMU_ISP_DW_TIM, 0x00020202),
+    K230_RMU_TIM(K230_RMU_DPU_TIM, 0x00020200),
+    K230_RMU_TIM(K230_RMU_DISP_TIM, 0x00040404),
+    K230_RMU_TIM(K230_RMU_V2P5D_TIM, 0x00040404),
+    K230_RMU_TIM(K230_RMU_AUDIO_TIM, 0x00000880),
+};
+
+/* 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;
+}
+
+/*
+ * Propagate a software reset request to the real peripherals the RMU controls.
+ * Only the two watchdogs are modelled today; asserting their PERI0 reset bits
+ * cold-resets the linked WDT device (a no-op if no device is linked).
+ */
+static void k230_rmu_propagate(K230RmuState *s, hwaddr offset, uint32_t v)
+{
+    if (offset != K230_RMU_PERI0_CTRL) {
+        return;
+    }
+    if ((v & K230_RMU_PERI0_WDT0_RST) && s->reset_targets[0]) {
+        trace_k230_rmu_target_reset(offset, 0);
+        device_cold_reset(s->reset_targets[0]);
+    }
+    if ((v & K230_RMU_PERI0_WDT1_RST) && s->reset_targets[1]) {
+        trace_k230_rmu_target_reset(offset, 1);
+        device_cold_reset(s->reset_targets[1]);
+    }
+}
+
+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, persist_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/persist masks for this register from its pairing
+     * table. persist_mask collects reset bits that do NOT self-clear (CPU1):
+     * those behave as write-enable-gated storage so software can deassert them.
+     */
+    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);
+        }
+        if (!r->pairs[i].self_clear) {
+            persist_mask |= BIT(r->pairs[i].reset_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 holds the 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, so only the low half can ever change; without write-enable every bit
+     * is directly writable (reserved bits are still dropped by writable_mask).
+     */
+    we_gate = r->has_we ? ((v >> K230_RMU_WE_SHIFT) & 0xffffu) : 0xffffffffu;
+
+    new_val = old;
+
+    /*
+     * (A) Writable storage: bits that are neither reset nor done bits are plain
+     *     read/write storage, plus any non-self-clearing reset bits (so the
+     *     CPU1 request can be asserted and later deasserted). Reserved bits
+     *     are dropped via writable_mask; for write-enable registers only the
+     *     low half is ever reachable (we_gate is confined to 16 bits above).
+     */
+    sw_mask = ((~reset_mask & ~done_mask) | persist_mask)
+              & we_gate & r->writable_mask;
+    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). Self-clearing requests then auto-clear so they can fire
+     *     again; non-self-clearing ones (CPU1) stay asserted until deasserted.
+     *     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);
+            }
+            if (r->pairs[i].self_clear) {
+                new_val &= ~rbit;
+            }
+        }
+    }
+
+    s->regs[offset / 4] = new_val;
+
+    /* (D) Drive a real cold reset of any linked peripheral (e.g. watchdogs). */
+    k230_rmu_propagate(s, offset, v);
+}
+
+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_hold(Object *obj, ResetType type)
+{
+    K230RmuState *s = K230_RMU(obj);
+
+    trace_k230_rmu_reset_device();
+
+    /*
+     * Start from all-zero, then apply each modelled register's documented reset
+     * value (K230 TRM chapter 2.1.5). Unmodelled offsets stay 0.
+     */
+    memset(s->regs, 0, sizeof(s->regs));
+    for (size_t i = 0; i < ARRAY_SIZE(k230_rmu_regs); i++) {
+        s->regs[k230_rmu_regs[i].offset / 4] = k230_rmu_regs[i].reset_val;
+    }
+}
+
+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 void k230_rmu_init(Object *obj)
+{
+    K230RmuState *s = K230_RMU(obj);
+
+    /*
+     * Optional links to the peripherals the RMU resets. The machine sets these
+     * before realize; leaving one unset simply disables that reset path.
+     */
+    object_property_add_link(obj, "wdt0", TYPE_DEVICE,
+                             (Object **)&s->reset_targets[0],
+                             qdev_prop_allow_set_link_before_realize,
+                             OBJ_PROP_LINK_STRONG);
+    object_property_add_link(obj, "wdt1", TYPE_DEVICE,
+                             (Object **)&s->reset_targets[1],
+                             qdev_prop_allow_set_link_before_realize,
+                             OBJ_PROP_LINK_STRONG);
+}
+
+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);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->realize = k230_rmu_realize;
+    /* Resettable API: a register-clearing device needs only phases.hold. */
+    rc->phases.hold = k230_rmu_reset_hold;
+    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),
+    .instance_init = k230_rmu_init,
+    .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..ee65409713 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -417,3 +417,10 @@ 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"
+k230_rmu_target_reset(uint64_t offset, unsigned target) "K230 RMU cold-reset target at offset 0x%" PRIx64 " target %u"
diff --git a/include/hw/misc/k230_rmu.h b/include/hw/misc/k230_rmu.h
new file mode 100644
index 0000000000..40ad70a14c
--- /dev/null
+++ b/include/hw/misc/k230_rmu.h
@@ -0,0 +1,126 @@
+/*
+ * 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. The driver names
+ * differ from the K230 TRM (chapter 2.1 "Reset"), but the offsets and bit
+ * layouts match the TRM's "*_RST_CTL" registers, named in the trailing comment.
+ * The documented reset values live in k230_rmu_regs[] in k230_rmu.c.
+ */
+#define K230_RMU_CPU0_CTRL     0x04   /* CPU0_RST_CTL */
+#define K230_RMU_CPU1_CTRL     0x0C   /* CPU1_RST_CTL */
+#define K230_RMU_AI_CTRL       0x14   /* AI_RST_CTL */
+#define K230_RMU_VPU_CTRL      0x1C   /* VPU_RST_CTL */
+#define K230_RMU_PERI0_CTRL    0x20   /* SOC_CTL_RST_CTL */
+/*
+ * PERI0 (SOC_CTL_RST_CTL) software-reset bits for the two watchdogs. In the TRM
+ * these are wdt_0_reset (bit12) / wdt_1_reset (bit13), whose reset value is 1
+ * ("reset disassert"). The model treats a write of these bits as "trigger a
+ * reset", driving a real cold reset of the linked WDT devices (see the
+ * "wdt0"/"wdt1" QOM links).
+ */
+#define K230_RMU_PERI0_WDT0_RST  BIT(12)
+#define K230_RMU_PERI0_WDT1_RST  BIT(13)
+#define K230_RMU_PERI1_CTRL    0x24   /* LOSYS_RST_CTL */
+#define K230_RMU_HISYS_CTRL    0x2C   /* HISYS_RST_CTL */
+#define K230_RMU_SDIO_CTRL     0x34   /* SDC_RST_CTL */
+#define K230_RMU_USB_CTRL      0x3C   /* USB_RST_CTL */
+#define K230_RMU_SPI_CTRL      0x44   /* SPI_RST_CTL */
+#define K230_RMU_SEC_CTRL      0x4C   /* SEC_RST_CTL */
+#define K230_RMU_DMA_CTRL      0x54   /* DMA_RST_CTL */
+#define K230_RMU_DECOMP_CTRL   0x5C   /* DECOMPRESS_RST_CTL */
+#define K230_RMU_SRAM_CTRL     0x64   /* SRAM_RST_CTL */
+#define K230_RMU_NONAI2D_CTRL  0x6C   /* NONAI2D_RST_CTL */
+#define K230_RMU_MCTL_CTRL     0x74   /* MCTL_RST_CTL */
+#define K230_RMU_ISP_CTRL      0x80   /* ISP_RST_CTL */
+#define K230_RMU_DPU_CTRL      0x88   /* DPU_RST_CTL */
+#define K230_RMU_DISP_CTRL     0x90   /* DISP_RST_CTL */
+#define K230_RMU_GPU_CTRL      0x98   /* V2P5D_RST_CTL */
+#define K230_RMU_AUDIO_CTRL    0xA4   /* AUDIO_RST_CTL */
+#define K230_RMU_SPI2AXI_CTRL  0xA8   /* SW_DONE (not in TRM) */
+
+/*
+ * Reset-time-control ("*_RST_TIM") registers. These interleave with the control
+ * registers above and are plain read/write timing storage with no side effects,
+ * so the model backs them with their documented reset value (see k230_rmu.c).
+ */
+#define K230_RMU_CPU0_TIM      0x00   /* CPU0_RST_TIM */
+#define K230_RMU_CPU1_TIM      0x08   /* CPU1_RST_TIM */
+#define K230_RMU_AI_TIM        0x10   /* AI_RST_TIM */
+#define K230_RMU_VPU_TIM       0x18   /* VPU_RST_TIM */
+#define K230_RMU_HISYS_TIM     0x28   /* HISYS_HCLK_TIM */
+#define K230_RMU_SDCTL_TIM     0x30   /* SDCTL_RST_TIM */
+#define K230_RMU_USB_TIM       0x38   /* USB_RST_TIM */
+#define K230_RMU_SPI_TIM       0x40   /* SPI_RST_TIM */
+#define K230_RMU_SEC_TIM       0x48   /* SEC_SYS_RST_TIM */
+#define K230_RMU_DMAC_TIM      0x50   /* DMAC_RST_TIM */
+#define K230_RMU_DECOMP_TIM    0x58   /* DECOMPRESS_RST_TIM */
+#define K230_RMU_SRAM_TIM      0x60   /* SRAM_RST_TIM */
+#define K230_RMU_NONAI2D_TIM   0x68   /* NONAI2D_RST_TIM */
+#define K230_RMU_MCTL_TIM      0x70   /* MCTL_RST_TIM */
+#define K230_RMU_ISP_TIM       0x78   /* ISP_RST_TIM */
+#define K230_RMU_ISP_DW_TIM    0x7C   /* ISP_DW_RST_TIM */
+#define K230_RMU_DPU_TIM       0x84   /* DPU_RST_TIM */
+#define K230_RMU_DISP_TIM      0x8C   /* DISP_SYS_RST_TIM */
+#define K230_RMU_V2P5D_TIM     0x94   /* V2P5D_SYS_RST_TIM */
+#define K230_RMU_AUDIO_TIM     0xA0   /* AUDIO_RST_TIM */
+
+/* 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, 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
+
+/* Devices the RMU can cold-reset: WDT0, WDT1 (via the "wdt0"/"wdt1" links). */
+#define K230_RMU_NUM_TARGETS   2
+
+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];
+
+    /*
+     * Optional links to the peripherals the RMU actually resets. Populated by
+     * the machine via the "wdt0"/"wdt1" QOM link properties; a NULL entry just
+     * means "no device connected" and the reset request is a no-op.
+     */
+    DeviceState *reset_targets[K230_RMU_NUM_TARGETS];
+};
+
+#endif /* HW_MISC_K230_RMU_H */
-- 
2.53.0
[PATCH v2 2/2] hw/riscv/k230: wire up the RMU device
Posted by Jack Wang 6 days, 15 hours ago
The previous commit added the K230 RMU device model but left it
unconnected. This commit integrates it into the K230 SoC:

- Instantiate K230RmuState in K230SoCState structure
- Realize and memory-map the device at 0x91101000 in k230_soc_realize()
- Connect the two watchdogs via the RMU "wdt0"/"wdt1" QOM links so that a
  software reset request in PERI0 cold-resets the real WDT devices
- Remove the create_unimplemented_device("rmu") stub
- Add CONFIG_K230_RMU=y selection in hw/riscv/Kconfig
- Update documentation to list RMU in supported devices
- Add the qtest and its MAINTAINERS entry

The qtest lives here rather than with the device model so the series stays
bisectable: it drives "-machine k230", which only exists once the device is
wired up. It covers all reset types, the documented reset values, the
reserved-bit masks, the CPU1 two-step assert/deassert sequence and the real
watchdog reset propagation.

Signed-off-by: Jack Wang <163wangjack@gmail.com>
---
 MAINTAINERS                 |   1 +
 docs/system/riscv/k230.rst  |   1 +
 hw/riscv/Kconfig            |   1 +
 hw/riscv/k230.c             |  15 ++-
 include/hw/riscv/k230.h     |   2 +
 tests/qtest/k230-rmu-test.c | 225 ++++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build     |   3 +-
 7 files changed, 244 insertions(+), 4 deletions(-)
 create mode 100644 tests/qtest/k230-rmu-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 80061ce66d..91305b86fe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1814,6 +1814,7 @@ F: hw/watchdog/k230_wdt.c
 F: include/hw/misc/k230_rmu.h
 F: include/hw/riscv/k230.h
 F: include/hw/watchdog/k230_wdt.h
+F: tests/qtest/k230-rmu-test.c
 F: tests/qtest/k230-wdt-test.c
 
 RX Machines
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..dc008eb6fa 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,17 @@ 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) */
+    /* Link the watchdogs so the RMU can reset them (before realize). */
+    object_property_set_link(OBJECT(&s->rmu), "wdt0",
+                             OBJECT(&s->wdt[0]), &error_abort);
+    object_property_set_link(OBJECT(&s->rmu), "wdt1",
+                             OBJECT(&s->wdt[1]), &error_abort);
+    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 +280,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;
 
diff --git a/tests/qtest/k230-rmu-test.c b/tests/qtest/k230-rmu-test.c
new file mode 100644
index 0000000000..a16c837382
--- /dev/null
+++ b/tests/qtest/k230-rmu-test.c
@@ -0,0 +1,225 @@
+/*
+ * 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);
+}
+
+/*
+ * Documented reset values (K230 TRM chapter 2.1.5, per-bit "Reset" column).
+ * The *_rst_done status bits reset to 0, so these differ from each register's
+ * summarised "Total Reset Value".
+ */
+static void test_reset_values(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    /* Control registers. */
+    g_assert_cmphex(rd(qts, K230_RMU_CPU0_CTRL),  ==, 0x00000000);
+    g_assert_cmphex(rd(qts, K230_RMU_CPU1_CTRL),  ==, 0x00000001);
+    g_assert_cmphex(rd(qts, K230_RMU_AI_CTRL),    ==, 0x00000000);
+    g_assert_cmphex(rd(qts, K230_RMU_PERI0_CTRL), ==, 0x000ff0ff);
+    g_assert_cmphex(rd(qts, K230_RMU_PERI1_CTRL), ==, 0x007e7fff);
+    g_assert_cmphex(rd(qts, K230_RMU_HISYS_CTRL), ==, 0x00000000);
+    g_assert_cmphex(rd(qts, K230_RMU_SRAM_CTRL),  ==, 0x00000002);
+    g_assert_cmphex(rd(qts, K230_RMU_ISP_CTRL),   ==, 0x0000039f);
+
+    /* A representative reset-time-control register. */
+    g_assert_cmphex(rd(qts, K230_RMU_CPU0_TIM),   ==, 0x00fff880);
+    g_assert_cmphex(rd(qts, K230_RMU_USB_TIM),    ==, 0x0008d288);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Reset-time-control registers are plain read/write storage: they come up with
+ * their documented value and accept arbitrary writes with no side effects.
+ */
+static void test_tim_storage(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    g_assert_cmphex(rd(qts, K230_RMU_MCTL_TIM), ==, 0x00000304);
+    wr(qts, K230_RMU_MCTL_TIM, 0x12345678);
+    g_assert_cmphex(rd(qts, K230_RMU_MCTL_TIM), ==, 0x12345678);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Reserved bits are read-only zero: a write to an undocumented bit position is
+ * dropped. ISP_CTL documents bits 0-9 and 28-29; bit 15 is reserved.
+ */
+static void test_reserved_bits(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+    uint32_t before = rd(qts, K230_RMU_ISP_CTRL);
+
+    wr(qts, K230_RMU_ISP_CTRL, before | BIT(15));
+    g_assert_cmphex(rd(qts, K230_RMU_ISP_CTRL) & BIT(15), ==, 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");
+
+    /* WDT0 / WDT1 reset bits */
+    wr(qts, K230_RMU_PERI0_CTRL, BIT(12) | BIT(13));
+    g_assert_cmphex(rd(qts, K230_RMU_PERI0_CTRL), ==, (BIT(12) | BIT(13)));
+
+    qtest_quit(qts);
+}
+
+/*
+ * CPU1 needs separate assert and deassert operations: unlike CPU0 its reset
+ * request bit does NOT self-clear. Assert (bit0 + strobe) latches done (bit12)
+ * and keeps bit0 set; deassert (strobe only, bit0 low) clears bit0.
+ */
+static void test_cpu1_two_step(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    /* Assert: request bit0 stays set AND done bit12 latches. */
+    wr(qts, K230_RMU_CPU1_CTRL,
+       K230_RMU_CPU_RESET | (K230_RMU_CPU_RESET << K230_RMU_WE_SHIFT));
+    g_assert_cmphex(rd(qts, K230_RMU_CPU1_CTRL), ==,
+                    K230_RMU_CPU_RESET | K230_RMU_CPU_DONE);
+
+    /* Deassert: drive bit0 low with its strobe; bit0 clears, done stays. */
+    wr(qts, K230_RMU_CPU1_CTRL, K230_RMU_CPU_RESET << K230_RMU_WE_SHIFT);
+    g_assert_cmphex(rd(qts, K230_RMU_CPU1_CTRL), ==, K230_RMU_CPU_DONE);
+
+    /* Done is write-1-to-clear (gated by its strobe). */
+    wr(qts, K230_RMU_CPU1_CTRL,
+       K230_RMU_CPU_DONE | (K230_RMU_CPU_DONE << K230_RMU_WE_SHIFT));
+    g_assert_cmphex(rd(qts, K230_RMU_CPU1_CTRL), ==, 0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Reset propagation: asserting a watchdog reset line in PERI0_CTRL performs a
+ * real cold reset of the linked WDT device. Put WDT0 into a non-default state
+ * (TORR != 0), pulse the RMU, and confirm WDT0 came back to its reset value.
+ */
+#define K230_WDT0_BASE 0x91106000ULL
+#define K230_WDT_TORR  0x04
+
+static void test_wdt_reset_propagation(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    /* Dirty WDT0: TORR reset value is 0, write a non-zero timeout range. */
+    qtest_writel(qts, K230_WDT0_BASE + K230_WDT_TORR, 0xf);
+    g_assert_cmphex(qtest_readl(qts, K230_WDT0_BASE + K230_WDT_TORR), ==, 0xf);
+
+    /* Assert WDT0's software reset line through the RMU (PERI0 bit12). */
+    wr(qts, K230_RMU_PERI0_CTRL, K230_RMU_PERI0_WDT0_RST);
+
+    /* WDT0 must be back to its reset value. */
+    g_assert_cmphex(qtest_readl(qts, K230_WDT0_BASE + K230_WDT_TORR), ==, 0);
+
+    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/tim_storage",    test_tim_storage);
+    qtest_add_func("/k230/rmu/reserved_bits",  test_reserved_bits);
+    qtest_add_func("/k230/rmu/cpu_we",         test_cpu_write_enable);
+    qtest_add_func("/k230/rmu/cpu1_two_step",  test_cpu1_two_step);
+    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);
+    qtest_add_func("/k230/rmu/wdt_reset_prop", test_wdt_reset_propagation);
+
+    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
Re: [RFC PATCH 0/2] *** Add k230 reset management unit support ***
Posted by Jack wang 1 week, 3 days ago
Hi all,

A polite ping for this RFC series. 
Could anyone please take a look when you have a moment? Any feedback or guidance would be greatly appreciated.

Thanks,
Jack Wang

> 2026年7月9日 11:12,jack wang <163wangjack@gmail.com> 写道:
> 
> 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
>