* [PATCH v7 0/5] Add support for K230 board
@ 2026-05-11 16:29 Chao Liu
2026-05-11 16:29 ` [PATCH v7 1/5] target/riscv: add thead-c908 cpu support Chao Liu
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: Chao Liu @ 2026-05-11 16:29 UTC (permalink / raw)
To: Chao Liu, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier
Cc: qemu-devel, qemu-riscv
This patch series adds support for U-Boot + OpenSBI + standard Linux kernel on
K230 board. Thanks to Peng Jiang, Mig Yang, Renzao Ren, Yao Zi for their help.
The current patchset fixes some bug and tag errors, Thanks to Alistair and
Conor for the review.
Test command with the direct Linux boot:
```
$QEMU -M k230 \
-kernel [Image] \
-dtb [k230-qemu.dtb] \
-initrd [rootfs.cpio.gz] \
-nographic
```
The k230-boot-assets repo [1] provides K230 Linux kernel images built with
k230-sdk and Yocto.
For more information, see docs/system/riscv/k230.rst.
The GitLab CI result [2] passed all cases.
PATCH v7 changelog:
- Patchset: Removed invalid tags for patchset.
- Patchset: Updated the K230 datasheet link in the patchset comment header.
- Patch 2: Removed the unnecessary changes to MAINTAINERS.
- Patch 2: Fixed an Oops caused by accesses to an unimpl UART MMIO address.
- Patch 2: Adjusted the K230 machine hart count to 1.
- Patch 2: Supported direct boot linux with `-kernel`.
- Patch 3: Aligned the WDT interrupt number with the K230 datasheet.
- Patch 5: Updated k230.rst Linux boot docs.
PATCH v6 changelog:
- Patchset: Rebased on the latest Alistair's riscv-to-apply.next branch [3].
- Patch 4: Picked up Fabiano's Acked-by.
PATCH v5 changelog:
- Patchset: Rebased on Alistair's riscv-to-apply.next branch.
- Patch 2: Fixed reset vector ROM jump to trap-handler bug.
PATCH v4 changelog:
- Patchset: Rebased on the latest master branch.
- Patchset: No functional changes from v3.
PATCH v3 changelog:
- Patch 1: Align T-Head C908 CPU's RISC-V extension with XUANTIE-QEMU.
- Patch 2: Adjust PLIC and CLINT addresses to match K230 datasheet.
PATCH v2 changelog:
- Patch 1: Add Svpbmt extension support for the T-Head C908 CPU.
- Patch 2: Move the k230.rst definition from MAINTAINERS to Patch 5.
- Patch 5: Apply Daniel's bugfix to build the k230 documentation successfully.
PATCH v1 changelog:
- Patch 1: Add T-Head C908 and C908v CPU support.
- Patch 2: Add K230 board initial support(big core is not supported yet).
- Patch 3: Add Programmable Watchdog Timer (WDT) peripheral support.
- Patch 4: Add QEMU test for K230 watchdog.
- Patch 5: Add documentation for K230 machine.
---
Link:
[1] https://github.com/zevorn/k230-boot-assets
[2] https://gitlab.com/chao23.liu/qemu/-/pipelines/2515556858
[3] https://github.com/alistair23/qemu/tree/riscv-to-apply.next
Thanks,
Chao
Chao Liu (5):
target/riscv: add thead-c908 cpu support
hw/riscv: add k230 board initial support
hw/watchdog: add k230 watchdog initial support
tests/qtest: add test for K230 watchdog
docs/system/riscv: add documentation for k230 machine
MAINTAINERS | 11 +
docs/system/riscv/k230.rst | 113 +++++++
docs/system/target-riscv.rst | 1 +
hw/riscv/Kconfig | 11 +
hw/riscv/k230.c | 524 +++++++++++++++++++++++++++++++++
hw/riscv/meson.build | 2 +-
hw/watchdog/Kconfig | 4 +
hw/watchdog/k230_wdt.c | 296 +++++++++++++++++++
hw/watchdog/meson.build | 1 +
hw/watchdog/trace-events | 9 +
include/hw/riscv/k230.h | 149 ++++++++++
include/hw/watchdog/k230_wdt.h | 121 ++++++++
target/riscv/cpu-qom.h | 2 +
target/riscv/cpu.c | 51 ++++
target/riscv/th_csr.c | 380 +++++++++++++++++++++++-
tests/qtest/k230-wdt-test.c | 189 ++++++++++++
tests/qtest/meson.build | 3 +-
17 files changed, 1864 insertions(+), 3 deletions(-)
create mode 100644 docs/system/riscv/k230.rst
create mode 100644 hw/riscv/k230.c
create mode 100644 hw/watchdog/k230_wdt.c
create mode 100644 include/hw/riscv/k230.h
create mode 100644 include/hw/watchdog/k230_wdt.h
create mode 100644 tests/qtest/k230-wdt-test.c
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 1/5] target/riscv: add thead-c908 cpu support
2026-05-11 16:29 [PATCH v7 0/5] Add support for K230 board Chao Liu
@ 2026-05-11 16:29 ` Chao Liu
2026-05-11 16:29 ` [PATCH v7 2/5] hw/riscv: add k230 board initial support Chao Liu
` (4 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Chao Liu @ 2026-05-11 16:29 UTC (permalink / raw)
To: Chao Liu, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier
Cc: qemu-devel, qemu-riscv, Chao Liu, Daniel Henrique Barboza,
Peng Jiang
From: Chao Liu <chao.liu@zevorn.cn>
The C908 processor is based on the RV64GCB[V] instruction
set, compatible to RVA22 Profile and implements the XIE
(XuanTie Instruction Extension) technology.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
Suggested-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com>
Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
Tested-by: Peng Jiang <3160104094@zju.edu.cn>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
---
target/riscv/cpu-qom.h | 2 +
target/riscv/cpu.c | 51 ++++++
target/riscv/th_csr.c | 380 ++++++++++++++++++++++++++++++++++++++++-
3 files changed, 432 insertions(+), 1 deletion(-)
diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.h
index 30dcdcfaae..1a28f1369c 100644
--- a/target/riscv/cpu-qom.h
+++ b/target/riscv/cpu-qom.h
@@ -52,6 +52,8 @@
#define TYPE_RISCV_CPU_SIFIVE_U34 RISCV_CPU_TYPE_NAME("sifive-u34")
#define TYPE_RISCV_CPU_SIFIVE_U54 RISCV_CPU_TYPE_NAME("sifive-u54")
#define TYPE_RISCV_CPU_THEAD_C906 RISCV_CPU_TYPE_NAME("thead-c906")
+#define TYPE_RISCV_CPU_THEAD_C908 RISCV_CPU_TYPE_NAME("thead-c908")
+#define TYPE_RISCV_CPU_THEAD_C908V RISCV_CPU_TYPE_NAME("thead-c908v")
#define TYPE_RISCV_CPU_VEYRON_V1 RISCV_CPU_TYPE_NAME("veyron-v1")
#define TYPE_RISCV_CPU_TT_ASCALON RISCV_CPU_TYPE_NAME("tt-ascalon")
#define TYPE_RISCV_CPU_XIANGSHAN_NANHU RISCV_CPU_TYPE_NAME("xiangshan-nanhu")
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index ce15a17c37..4f6637b279 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -3151,6 +3151,57 @@ static const TypeInfo riscv_cpu_type_infos[] = {
#endif
),
+ DEFINE_RISCV_CPU(TYPE_RISCV_CPU_THEAD_C908, TYPE_RISCV_VENDOR_CPU,
+ .misa_mxl_max = MXL_RV64,
+ .misa_ext = RVI | RVM | RVA | RVF | RVD | RVC | RVS | RVU,
+ .priv_spec = PRIV_VERSION_1_12_0,
+
+ /* ISA extensions */
+ .cfg.ext_xtheadba = true,
+ .cfg.ext_xtheadbb = true,
+ .cfg.ext_xtheadbs = true,
+ .cfg.ext_xtheadcmo = true,
+ .cfg.ext_xtheadcondmov = true,
+ .cfg.ext_xtheadfmv = true,
+ .cfg.ext_xtheadfmemidx = true,
+ .cfg.ext_xtheadmac = true,
+ .cfg.ext_xtheadmemidx = true,
+ .cfg.ext_xtheadmempair = true,
+ .cfg.ext_xtheadsync = true,
+ .cfg.ext_smepmp = true,
+ .cfg.ext_sscofpmf = true,
+ .cfg.ext_sstc = true,
+ .cfg.ext_svpbmt = true,
+ .cfg.ext_svinval = true,
+ .cfg.ext_svnapot = true,
+ .cfg.ext_zba = true,
+ .cfg.ext_zbb = true,
+ .cfg.ext_zbc = true,
+ .cfg.ext_zbs = true,
+ .cfg.ext_zkt = true,
+ .cfg.ext_zbkc = true,
+ .cfg.ext_zicsr = true,
+ .cfg.ext_zifencei = true,
+ .cfg.ext_zihintpause = true,
+ .cfg.ext_zicbom = true,
+ .cfg.ext_zicboz = true,
+
+ .cfg.pmp = true,
+ .cfg.mmu = true,
+ .cfg.max_satp_mode = VM_1_10_SV48,
+
+ .cfg.marchid = 0x8d143000,
+ .cfg.mvendorid = THEAD_VENDOR_ID,
+#ifndef CONFIG_USER_ONLY
+ .custom_csrs = th_csr_list,
+#endif
+ ),
+
+ DEFINE_RISCV_CPU(TYPE_RISCV_CPU_THEAD_C908V, TYPE_RISCV_CPU_THEAD_C908,
+ .misa_ext = RVI | RVM | RVA | RVF | RVD | RVC | RVS | RVU | RVV,
+ .vext_spec = VEXT_VERSION_1_00_0,
+ ),
+
DEFINE_RISCV_CPU(TYPE_RISCV_CPU_TT_ASCALON, TYPE_RISCV_VENDOR_CPU,
.misa_mxl_max = MXL_RV64,
.misa_ext = RVG | RVC | RVS | RVU | RVH | RVV,
diff --git a/target/riscv/th_csr.c b/target/riscv/th_csr.c
index 49eb7bbab5..e19cab5414 100644
--- a/target/riscv/th_csr.c
+++ b/target/riscv/th_csr.c
@@ -2,6 +2,9 @@
* T-Head-specific CSRs.
*
* Copyright (c) 2024 VRULL GmbH
+ * Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
+ *
+ * For more information, see XuanTie-C908-UserManual_xrvm_20240530.pdf
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -20,13 +23,88 @@
#include "cpu.h"
#include "cpu_vendorid.h"
-#define CSR_TH_SXSTATUS 0x5c0
+/* Extended M-mode control registers of T-Head */
+#define CSR_TH_MXSTATUS 0x7c0
+#define CSR_TH_MHCR 0x7c1
+#define CSR_TH_MCOR 0x7c2
+#define CSR_TH_MCCR2 0x7c3
+#define CSR_TH_MHINT 0x7c5
+#define CSR_TH_MRVBR 0x7c7
+#define CSR_TH_MCOUNTERWEN 0x7c9
+#define CSR_TH_MCOUNTERINTEN 0x7ca
+#define CSR_TH_MCOUNTEROF 0x7cb
+#define CSR_TH_MCINS 0x7d2
+#define CSR_TH_MCINDEX 0x7d3
+#define CSR_TH_MCDATA0 0x7d4
+#define CSR_TH_MCDATA1 0x7d5
+#define CSR_TH_MSMPR 0x7f3
+#define CSR_TH_CPUID 0xfc0
+#define CSR_TH_MAPBADDR 0xfc1
+
+/* TH_MXSTATUS bits */
+#define TH_MXSTATUS_UCME BIT(16)
+#define TH_MXSTATUS_MAEE BIT(21)
+#define TH_MXSTATUS_THEADISAEE BIT(22)
+
+/* Extended S-mode control registers of T-Head */
+#define CSR_TH_SXSTATUS 0x5c0
+#define CSR_TH_SHCR 0x5c1
+#define CSR_TH_SCER2 0x5c2
+#define CSR_TH_SCER 0x5c3
+#define CSR_TH_SCOUNTERINTEN 0x5c4
+#define CSR_TH_SCOUNTEROF 0x5c5
+#define CSR_TH_SCYCLE 0x5e0
+#define CSR_TH_SHPMCOUNTER3 0x5e3
+#define CSR_TH_SHPMCOUNTER4 0x5e4
+#define CSR_TH_SHPMCOUNTER5 0x5e5
+#define CSR_TH_SHPMCOUNTER6 0x5e6
+#define CSR_TH_SHPMCOUNTER7 0x5e7
+#define CSR_TH_SHPMCOUNTER8 0x5e8
+#define CSR_TH_SHPMCOUNTER9 0x5e9
+#define CSR_TH_SHPMCOUNTER10 0x5ea
+#define CSR_TH_SHPMCOUNTER11 0x5eb
+#define CSR_TH_SHPMCOUNTER12 0x5ec
+#define CSR_TH_SHPMCOUNTER13 0x5ed
+#define CSR_TH_SHPMCOUNTER14 0x5ee
+#define CSR_TH_SHPMCOUNTER15 0x5ef
+#define CSR_TH_SHPMCOUNTER16 0x5f0
+#define CSR_TH_SHPMCOUNTER17 0x5f1
+#define CSR_TH_SHPMCOUNTER18 0x5f2
+#define CSR_TH_SHPMCOUNTER19 0x5f3
+#define CSR_TH_SHPMCOUNTER20 0x5f4
+#define CSR_TH_SHPMCOUNTER21 0x5f5
+#define CSR_TH_SHPMCOUNTER22 0x5f6
+#define CSR_TH_SHPMCOUNTER23 0x5f7
+#define CSR_TH_SHPMCOUNTER24 0x5f8
+#define CSR_TH_SHPMCOUNTER25 0x5f9
+#define CSR_TH_SHPMCOUNTER26 0x5fa
+#define CSR_TH_SHPMCOUNTER27 0x5fb
+#define CSR_TH_SHPMCOUNTER28 0x5fc
+#define CSR_TH_SHPMCOUNTER29 0x5fd
+#define CSR_TH_SHPMCOUNTER30 0x5fe
+#define CSR_TH_SHPMCOUNTER31 0x5ff
+#define CSR_TH_SMIR 0x9c0
+#define CSR_TH_SMLO0 0x9c1
+#define CSR_TH_SMEH 0x9c2
+#define CSR_TH_SMCIR 0x9c3
+
+/* Extended U-mode control registers of T-Head */
+#define CSR_TH_FXCR 0x800
/* TH_SXSTATUS bits */
#define TH_SXSTATUS_UCME BIT(16)
#define TH_SXSTATUS_MAEE BIT(21)
#define TH_SXSTATUS_THEADISAEE BIT(22)
+static RISCVException mmode(CPURISCVState *env, int csrno)
+{
+ if (riscv_has_ext(env, RVM)) {
+ return RISCV_EXCP_NONE;
+ }
+
+ return RISCV_EXCP_ILLEGAL_INST;
+}
+
static RISCVException smode(CPURISCVState *env, int csrno)
{
if (riscv_has_ext(env, RVS)) {
@@ -36,11 +114,31 @@ static RISCVException smode(CPURISCVState *env, int csrno)
return RISCV_EXCP_ILLEGAL_INST;
}
+static RISCVException any(CPURISCVState *env, int csrno)
+{
+ return RISCV_EXCP_NONE;
+}
+
static bool test_thead_mvendorid(RISCVCPU *cpu)
{
return cpu->cfg.mvendorid == THEAD_VENDOR_ID;
}
+static RISCVException read_th_mxstatus(CPURISCVState *env, int csrno,
+ target_ulong *val)
+{
+ /* We don't set MAEE here, because QEMU does not implement MAEE. */
+ *val = TH_MXSTATUS_UCME | TH_MXSTATUS_THEADISAEE;
+ return RISCV_EXCP_NONE;
+}
+
+static RISCVException read_unimp_th_csr(CPURISCVState *env, int csrno,
+ target_ulong *val)
+{
+ *val = 0;
+ return RISCV_EXCP_NONE;
+}
+
static RISCVException read_th_sxstatus(CPURISCVState *env, int csrno,
target_ulong *val)
{
@@ -50,10 +148,290 @@ static RISCVException read_th_sxstatus(CPURISCVState *env, int csrno,
}
const RISCVCSR th_csr_list[] = {
+ {
+ .csrno = CSR_TH_MXSTATUS,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mxstatus", mmode, read_th_mxstatus }
+ },
+ {
+ .csrno = CSR_TH_MHCR,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mhcr", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MCOR,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mcor", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MCCR2,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mccr2", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MHINT,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mhint", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MRVBR,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mrvbr", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MCOUNTERWEN,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mcounterwen", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MCOUNTERINTEN,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mcounterinten", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MCOUNTEROF,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mcounterof", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MCINS,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mcins", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MCINDEX,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mcindex", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MCDATA0,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mcdata0", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MCDATA1,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mcdata1", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MSMPR,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.msmpr", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_CPUID,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.cpuid", mmode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_MAPBADDR,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.mapbaddr", mmode, read_unimp_th_csr }
+ },
{
.csrno = CSR_TH_SXSTATUS,
.insertion_test = test_thead_mvendorid,
.csr_ops = { "th.sxstatus", smode, read_th_sxstatus }
},
+ {
+ .csrno = CSR_TH_SHCR,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shcr", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SCER2,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.scer2", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SCER,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.scer", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SCOUNTERINTEN,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.scounterinten", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SCOUNTEROF,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.scounterof", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SCYCLE,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.scycle", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER3,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter3", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER4,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter4", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER5,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter5", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER6,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter6", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER7,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter7", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER8,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter8", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER9,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter9", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER10,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter10", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER11,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter11", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER12,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter12", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER13,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter13", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER14,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter14", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER15,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter15", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER16,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter16", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER17,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter17", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER18,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter18", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER19,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter19", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER20,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter20", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER21,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter21", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER22,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter22", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER23,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter23", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER24,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter24", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER25,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter25", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER26,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter26", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER27,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter27", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER28,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter28", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER29,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter29", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER30,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter30", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SHPMCOUNTER31,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.shpmcounter31", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SMIR,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.smir", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SMLO0,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.smlo0", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SMEH,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.smeh", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_SMCIR,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.smcir", smode, read_unimp_th_csr }
+ },
+ {
+ .csrno = CSR_TH_FXCR,
+ .insertion_test = test_thead_mvendorid,
+ .csr_ops = { "th.fxcr", any, read_unimp_th_csr }
+ },
{ }
};
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 2/5] hw/riscv: add k230 board initial support
2026-05-11 16:29 [PATCH v7 0/5] Add support for K230 board Chao Liu
2026-05-11 16:29 ` [PATCH v7 1/5] target/riscv: add thead-c908 cpu support Chao Liu
@ 2026-05-11 16:29 ` Chao Liu
2026-06-11 4:04 ` Alistair Francis
2026-05-11 16:29 ` [PATCH v7 3/5] hw/watchdog: add k230 watchdog " Chao Liu
` (3 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Chao Liu @ 2026-05-11 16:29 UTC (permalink / raw)
To: Chao Liu, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier
Cc: qemu-devel, qemu-riscv, Peng Jiang
K230 Board compatible with Kendryte K230 SDK.
Preliminarily supports the C908 small core, which can run U-Boot and
Linux kernels compiled by the K230 SDK.
The K230 boot flow provides its device tree from firmware or software.
QEMU does not generate a K230 DTB; users can pass one with -dtb for
direct Linux boot, or rely on firmware/kernel built-in DTB for other
payloads.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
Tested-by: Peng Jiang <3160104094@zju.edu.cn>
---
MAINTAINERS | 7 +
hw/riscv/Kconfig | 10 +
hw/riscv/k230.c | 506 ++++++++++++++++++++++++++++++++++++++++
hw/riscv/meson.build | 2 +-
include/hw/riscv/k230.h | 145 ++++++++++++
5 files changed, 669 insertions(+), 1 deletion(-)
create mode 100644 hw/riscv/k230.c
create mode 100644 include/hw/riscv/k230.h
diff --git a/MAINTAINERS b/MAINTAINERS
index f109e46172..196b476abe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1780,6 +1780,13 @@ F: docs/system/riscv/xiangshan-kunminghu.rst
F: hw/riscv/xiangshan_kmh.c
F: include/hw/riscv/xiangshan_kmh.h
+K230 Machines
+M: Chao Liu <chao.liu.zevorn@gmail.com>
+L: qemu-riscv@nongnu.org
+S: Maintained
+F: hw/riscv/k230.c
+F: include/hw/riscv/k230.h
+
RX Machines
-----------
rx-gdbsim
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 0222c93f87..b1a7357866 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -134,3 +134,13 @@ config MIPS_BOSTON_AIA
default y
select PCI_EXPRESS
select PCI_EXPRESS_XILINX
+
+config K230
+ bool
+ default y
+ depends on RISCV64
+ select RISCV_ACLINT
+ select RISCV_APLIC
+ select RISCV_IMSIC
+ select SERIAL_MM
+ select UNIMP
diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
new file mode 100644
index 0000000000..327355b565
--- /dev/null
+++ b/hw/riscv/k230.c
@@ -0,0 +1,506 @@
+/*
+ * QEMU RISC-V Virt Board Compatible with Kendryte K230 SDK
+ *
+ * Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Provides a board compatible with the Kendryte K230 SDK
+ *
+ * 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
+ *
+ * For more information, see <https://www.kendryte.com/en/proDetail/230>
+ */
+
+#include "qemu/osdep.h"
+#include "cpu-qom.h"
+#include "qemu/cutils.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "system/device_tree.h"
+#include "system/system.h"
+#include "system/memory.h"
+#include "target/riscv/cpu.h"
+#include "hw/core/loader.h"
+#include "hw/core/sysbus.h"
+#include "hw/riscv/k230.h"
+#include "hw/riscv/boot.h"
+#include "hw/intc/riscv_aclint.h"
+#include "hw/intc/sifive_plic.h"
+#include "hw/char/serial-mm.h"
+#include "hw/misc/unimp.h"
+
+/* Align K230_SDK k230_canmv_defconfig */
+#define K230_DIRECT_OPENSBI_ADDR 0x8000000
+#define K230_DIRECT_KERNEL_ADDR 0x8200000
+#define K230_DIRECT_DTB_ADDR 0xa000000
+
+static const MemMapEntry memmap[] = {
+ [K230_DEV_DDRC] = { 0x00000000, 0x80000000 },
+ [K230_DEV_KPU_L2_CACHE] = { 0x80000000, 0x00200000 },
+ [K230_DEV_SRAM] = { 0x80200000, 0x00200000 },
+ [K230_DEV_KPU_CFG] = { 0x80400000, 0x00000800 },
+ [K230_DEV_FFT] = { 0x80400800, 0x00000400 },
+ [K230_DEV_AI_2D_ENGINE] = { 0x80400C00, 0x00000800 },
+ [K230_DEV_GSDMA] = { 0x80800000, 0x00004000 },
+ [K230_DEV_DMA] = { 0x80804000, 0x00004000 },
+ [K230_DEV_DECOMP_GZIP] = { 0x80808000, 0x00004000 },
+ [K230_DEV_NON_AI_2D] = { 0x8080C000, 0x00004000 },
+ [K230_DEV_ISP] = { 0x90000000, 0x00008000 },
+ [K230_DEV_DEWARP] = { 0x90008000, 0x00001000 },
+ [K230_DEV_RX_CSI] = { 0x90009000, 0x00002000 },
+ [K230_DEV_H264] = { 0x90400000, 0x00010000 },
+ [K230_DEV_2P5D] = { 0x90800000, 0x00040000 },
+ [K230_DEV_VO] = { 0x90840000, 0x00010000 },
+ [K230_DEV_VO_CFG] = { 0x90850000, 0x00001000 },
+ [K230_DEV_3D_ENGINE] = { 0x90A00000, 0x00000800 },
+ [K230_DEV_PMU] = { 0x91000000, 0x00000C00 },
+ [K230_DEV_RTC] = { 0x91000C00, 0x00000400 },
+ [K230_DEV_CMU] = { 0x91100000, 0x00001000 },
+ [K230_DEV_RMU] = { 0x91101000, 0x00001000 },
+ [K230_DEV_BOOT] = { 0x91102000, 0x00001000 },
+ [K230_DEV_PWR] = { 0x91103000, 0x00001000 },
+ [K230_DEV_MAILBOX] = { 0x91104000, 0x00001000 },
+ [K230_DEV_IOMUX] = { 0x91105000, 0x00000800 },
+ [K230_DEV_TIMER] = { 0x91105800, 0x00000800 },
+ [K230_DEV_WDT0] = { 0x91106000, 0x00000800 },
+ [K230_DEV_WDT1] = { 0x91106800, 0x00000800 },
+ [K230_DEV_TS] = { 0x91107000, 0x00000800 },
+ [K230_DEV_HDI] = { 0x91107800, 0x00000800 },
+ [K230_DEV_STC] = { 0x91108000, 0x00000800 },
+ [K230_DEV_BOOTROM] = { 0x91200000, 0x00010000 },
+ [K230_DEV_SECURITY] = { 0x91210000, 0x00008000 },
+ [K230_DEV_UART0] = { 0x91400000, 0x00001000 },
+ [K230_DEV_UART1] = { 0x91401000, 0x00001000 },
+ [K230_DEV_UART2] = { 0x91402000, 0x00001000 },
+ [K230_DEV_UART3] = { 0x91403000, 0x00001000 },
+ [K230_DEV_UART4] = { 0x91404000, 0x00001000 },
+ [K230_DEV_I2C0] = { 0x91405000, 0x00001000 },
+ [K230_DEV_I2C1] = { 0x91406000, 0x00001000 },
+ [K230_DEV_I2C2] = { 0x91407000, 0x00001000 },
+ [K230_DEV_I2C3] = { 0x91408000, 0x00001000 },
+ [K230_DEV_I2C4] = { 0x91409000, 0x00001000 },
+ [K230_DEV_PWM] = { 0x9140A000, 0x00001000 },
+ [K230_DEV_GPIO0] = { 0x9140B000, 0x00001000 },
+ [K230_DEV_GPIO1] = { 0x9140C000, 0x00001000 },
+ [K230_DEV_ADC] = { 0x9140D000, 0x00001000 },
+ [K230_DEV_CODEC] = { 0x9140E000, 0x00001000 },
+ [K230_DEV_I2S] = { 0x9140F000, 0x00001000 },
+ [K230_DEV_USB0] = { 0x91500000, 0x00010000 },
+ [K230_DEV_USB1] = { 0x91540000, 0x00010000 },
+ [K230_DEV_SD0] = { 0x91580000, 0x00001000 },
+ [K230_DEV_SD1] = { 0x91581000, 0x00001000 },
+ [K230_DEV_QSPI0] = { 0x91582000, 0x00001000 },
+ [K230_DEV_QSPI1] = { 0x91583000, 0x00001000 },
+ [K230_DEV_SPI] = { 0x91584000, 0x00001000 },
+ [K230_DEV_HI_SYS_CFG] = { 0x91585000, 0x00000400 },
+ [K230_DEV_DDRC_CFG] = { 0x98000000, 0x02000000 },
+ [K230_DEV_FLASH] = { 0xC0000000, 0x08000000 },
+ [K230_DEV_PLIC] = { 0xF00000000, 0x00400000 },
+ [K230_DEV_CLINT] = { 0xF04000000, 0x00400000 },
+};
+
+static void k230_soc_init(Object *obj)
+{
+ K230SoCState *s = RISCV_K230_SOC(obj);
+ RISCVHartArrayState *cpu0 = &s->c908_cpu;
+
+ object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY);
+ qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
+ qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
+ qdev_prop_set_uint64(DEVICE(cpu0), "resetvec",
+ memmap[K230_DEV_BOOTROM].base);
+}
+
+static DeviceState *k230_create_plic(int base_hartid, int hartid_count)
+{
+ g_autofree char *plic_hart_config = NULL;
+
+ /* Per-socket PLIC hart topology configuration string */
+ plic_hart_config = riscv_plic_hart_config_string(hartid_count);
+
+ /* Per-socket PLIC */
+ return sifive_plic_create(memmap[K230_DEV_PLIC].base,
+ plic_hart_config, hartid_count, base_hartid,
+ K230_PLIC_NUM_SOURCES,
+ K230_PLIC_NUM_PRIORITIES,
+ K230_PLIC_PRIORITY_BASE, K230_PLIC_PENDING_BASE,
+ K230_PLIC_ENABLE_BASE, K230_PLIC_ENABLE_STRIDE,
+ K230_PLIC_CONTEXT_BASE,
+ K230_PLIC_CONTEXT_STRIDE,
+ memmap[K230_DEV_PLIC].size);
+}
+
+static void k230_create_uart(MemoryRegion *sys_mem, DeviceState *plic,
+ int index)
+{
+ int uart_dev = K230_DEV_UART0 + index;
+ g_autofree char *name = g_strdup_printf("uart%d", index);
+
+ /* Cover the non-16550 part of the SDK's 0x1000 UART window. */
+ create_unimplemented_device(name, memmap[uart_dev].base,
+ memmap[uart_dev].size);
+
+ serial_mm_init(sys_mem, memmap[uart_dev].base, 2,
+ qdev_get_gpio_in(plic, K230_UART0_IRQ + index),
+ 399193, serial_hd(index), DEVICE_LITTLE_ENDIAN);
+}
+
+static void k230_soc_realize(DeviceState *dev, Error **errp)
+{
+ K230SoCState *s = RISCV_K230_SOC(dev);
+ MemoryRegion *sys_mem = get_system_memory();
+ int c908_cpus;
+
+ sysbus_realize(SYS_BUS_DEVICE(&s->c908_cpu), &error_fatal);
+
+ c908_cpus = s->c908_cpu.num_harts;
+
+ /* SRAM */
+ memory_region_init_ram(&s->sram, OBJECT(dev), "sram",
+ memmap[K230_DEV_SRAM].size, &error_fatal);
+ memory_region_add_subregion(sys_mem, memmap[K230_DEV_SRAM].base,
+ &s->sram);
+
+ /* BootROM */
+ memory_region_init_rom(&s->bootrom, OBJECT(dev), "bootrom",
+ memmap[K230_DEV_BOOTROM].size, &error_fatal);
+ memory_region_add_subregion(sys_mem, memmap[K230_DEV_BOOTROM].base,
+ &s->bootrom);
+
+ /* PLIC */
+ s->c908_plic = k230_create_plic(C908_CPU_HARTID, c908_cpus);
+
+ /* CLINT */
+ riscv_aclint_swi_create(memmap[K230_DEV_CLINT].base,
+ C908_CPU_HARTID, c908_cpus, false);
+ riscv_aclint_mtimer_create(memmap[K230_DEV_CLINT].base + 0x4000,
+ RISCV_ACLINT_DEFAULT_MTIMER_SIZE,
+ C908_CPU_HARTID, c908_cpus,
+ RISCV_ACLINT_DEFAULT_MTIMECMP,
+ RISCV_ACLINT_DEFAULT_MTIME,
+ RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ, true);
+
+ /* UART */
+ for (int i = 0; i < K230_UART_COUNT; i++) {
+ k230_create_uart(sys_mem, DEVICE(s->c908_plic), i);
+ }
+
+ /* unimplemented devices */
+ create_unimplemented_device("kpu.l2-cache",
+ memmap[K230_DEV_KPU_L2_CACHE].base,
+ memmap[K230_DEV_KPU_L2_CACHE].size);
+
+ create_unimplemented_device("kpu_cfg", memmap[K230_DEV_KPU_CFG].base,
+ memmap[K230_DEV_KPU_CFG].size);
+
+ create_unimplemented_device("fft", memmap[K230_DEV_FFT].base,
+ memmap[K230_DEV_FFT].size);
+
+ create_unimplemented_device("2d-engine.ai",
+ memmap[K230_DEV_AI_2D_ENGINE].base,
+ memmap[K230_DEV_AI_2D_ENGINE].size);
+
+ create_unimplemented_device("gsdma", memmap[K230_DEV_GSDMA].base,
+ memmap[K230_DEV_GSDMA].size);
+
+ create_unimplemented_device("dma", memmap[K230_DEV_DMA].base,
+ memmap[K230_DEV_DMA].size);
+
+ create_unimplemented_device("decomp-gzip",
+ memmap[K230_DEV_DECOMP_GZIP].base,
+ memmap[K230_DEV_DECOMP_GZIP].size);
+
+ create_unimplemented_device("2d-engine.non-ai",
+ memmap[K230_DEV_NON_AI_2D].base,
+ memmap[K230_DEV_NON_AI_2D].size);
+
+ create_unimplemented_device("isp", memmap[K230_DEV_ISP].base,
+ memmap[K230_DEV_ISP].size);
+
+ create_unimplemented_device("dewarp", memmap[K230_DEV_DEWARP].base,
+ memmap[K230_DEV_DEWARP].size);
+
+ create_unimplemented_device("rx-csi", memmap[K230_DEV_RX_CSI].base,
+ memmap[K230_DEV_RX_CSI].size);
+
+ create_unimplemented_device("vpu", memmap[K230_DEV_H264].base,
+ memmap[K230_DEV_H264].size);
+
+ create_unimplemented_device("gpu", memmap[K230_DEV_2P5D].base,
+ memmap[K230_DEV_2P5D].size);
+
+ create_unimplemented_device("vo", memmap[K230_DEV_VO].base,
+ memmap[K230_DEV_VO].size);
+
+ create_unimplemented_device("vo_cfg", memmap[K230_DEV_VO_CFG].base,
+ memmap[K230_DEV_VO_CFG].size);
+
+ create_unimplemented_device("3d-engine", memmap[K230_DEV_3D_ENGINE].base,
+ memmap[K230_DEV_3D_ENGINE].size);
+
+ create_unimplemented_device("pmu", memmap[K230_DEV_PMU].base,
+ memmap[K230_DEV_PMU].size);
+
+ create_unimplemented_device("rtc", memmap[K230_DEV_RTC].base,
+ memmap[K230_DEV_RTC].size);
+
+ 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);
+
+ create_unimplemented_device("pwr", memmap[K230_DEV_PWR].base,
+ memmap[K230_DEV_PWR].size);
+
+ create_unimplemented_device("ipcm", memmap[K230_DEV_MAILBOX].base,
+ memmap[K230_DEV_MAILBOX].size);
+
+ create_unimplemented_device("iomux", memmap[K230_DEV_IOMUX].base,
+ memmap[K230_DEV_IOMUX].size);
+
+ create_unimplemented_device("timer", memmap[K230_DEV_TIMER].base,
+ memmap[K230_DEV_TIMER].size);
+
+ create_unimplemented_device("wdt0", memmap[K230_DEV_WDT0].base,
+ memmap[K230_DEV_WDT0].size);
+
+ create_unimplemented_device("wdt1", memmap[K230_DEV_WDT1].base,
+ memmap[K230_DEV_WDT1].size);
+
+ create_unimplemented_device("ts", memmap[K230_DEV_TS].base,
+ memmap[K230_DEV_TS].size);
+
+ create_unimplemented_device("hdi", memmap[K230_DEV_HDI].base,
+ memmap[K230_DEV_HDI].size);
+
+ create_unimplemented_device("stc", memmap[K230_DEV_STC].base,
+ memmap[K230_DEV_STC].size);
+
+ create_unimplemented_device("security", memmap[K230_DEV_SECURITY].base,
+ memmap[K230_DEV_SECURITY].size);
+
+ create_unimplemented_device("i2c0", memmap[K230_DEV_I2C0].base,
+ memmap[K230_DEV_I2C0].size);
+
+ create_unimplemented_device("i2c1", memmap[K230_DEV_I2C1].base,
+ memmap[K230_DEV_I2C1].size);
+
+ create_unimplemented_device("i2c2", memmap[K230_DEV_I2C2].base,
+ memmap[K230_DEV_I2C2].size);
+
+ create_unimplemented_device("i2c3", memmap[K230_DEV_I2C3].base,
+ memmap[K230_DEV_I2C3].size);
+
+ create_unimplemented_device("i2c4", memmap[K230_DEV_I2C4].base,
+ memmap[K230_DEV_I2C4].size);
+
+ create_unimplemented_device("pwm", memmap[K230_DEV_PWM].base,
+ memmap[K230_DEV_PWM].size);
+
+ create_unimplemented_device("gpio0", memmap[K230_DEV_GPIO0].base,
+ memmap[K230_DEV_GPIO0].size);
+
+ create_unimplemented_device("gpio1", memmap[K230_DEV_GPIO1].base,
+ memmap[K230_DEV_GPIO1].size);
+
+ create_unimplemented_device("adc", memmap[K230_DEV_ADC].base,
+ memmap[K230_DEV_ADC].size);
+
+ create_unimplemented_device("codec", memmap[K230_DEV_CODEC].base,
+ memmap[K230_DEV_CODEC].size);
+
+ create_unimplemented_device("i2s", memmap[K230_DEV_I2S].base,
+ memmap[K230_DEV_I2S].size);
+
+ create_unimplemented_device("usb0", memmap[K230_DEV_USB0].base,
+ memmap[K230_DEV_USB0].size);
+
+ create_unimplemented_device("usb1", memmap[K230_DEV_USB1].base,
+ memmap[K230_DEV_USB1].size);
+
+ create_unimplemented_device("sd0", memmap[K230_DEV_SD0].base,
+ memmap[K230_DEV_SD0].size);
+
+ create_unimplemented_device("sd1", memmap[K230_DEV_SD1].base,
+ memmap[K230_DEV_SD1].size);
+
+ create_unimplemented_device("qspi0", memmap[K230_DEV_QSPI0].base,
+ memmap[K230_DEV_QSPI0].size);
+
+ create_unimplemented_device("qspi1", memmap[K230_DEV_QSPI1].base,
+ memmap[K230_DEV_QSPI1].size);
+
+ create_unimplemented_device("spi", memmap[K230_DEV_SPI].base,
+ memmap[K230_DEV_SPI].size);
+
+ create_unimplemented_device("hi_sys_cfg", memmap[K230_DEV_HI_SYS_CFG].base,
+ memmap[K230_DEV_HI_SYS_CFG].size);
+
+ create_unimplemented_device("ddrc_cfg", memmap[K230_DEV_DDRC_CFG].base,
+ memmap[K230_DEV_DDRC_CFG].size);
+
+ create_unimplemented_device("flash", memmap[K230_DEV_FLASH].base,
+ memmap[K230_DEV_FLASH].size);
+}
+
+static void k230_soc_class_init(ObjectClass *oc, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = k230_soc_realize;
+}
+
+static const TypeInfo k230_soc_type_info = {
+ .name = TYPE_RISCV_K230_SOC,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(K230SoCState),
+ .instance_init = k230_soc_init,
+ .class_init = k230_soc_class_init,
+};
+
+static void k230_soc_register_types(void)
+{
+ type_register_static(&k230_soc_type_info);
+}
+
+type_init(k230_soc_register_types)
+
+static void k230_direct_boot(K230MachineState *s, MachineState *machine)
+{
+ const char *firmware_name = riscv_default_firmware_name(&s->soc.c908_cpu);
+ RISCVBootInfo boot_info = {0};
+ hwaddr start_addr = K230_DIRECT_OPENSBI_ADDR;
+ hwaddr firmware_end_addr = 0;
+ hwaddr kernel_entry = 0;
+ int fdt_size = 0;
+
+ if (machine->firmware && !strcmp(machine->firmware, "none")) {
+ error_report("K230 direct boot requires OpenSBI firmware; omit "
+ "-bios none or pass OpenSBI with -bios");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!machine->dtb) {
+ error_report("K230 direct boot requires -dtb");
+ exit(EXIT_FAILURE);
+ }
+
+ machine->fdt = load_device_tree(machine->dtb, &fdt_size);
+ if (!machine->fdt) {
+ error_report("load_device_tree() failed");
+ exit(EXIT_FAILURE);
+ }
+
+ qemu_fdt_add_path(machine->fdt, "/chosen");
+
+ riscv_boot_info_init(&boot_info, &s->soc.c908_cpu);
+ riscv_load_kernel(machine, &boot_info, K230_DIRECT_KERNEL_ADDR, true, NULL);
+ kernel_entry = boot_info.image_low_addr;
+
+ riscv_load_fdt(K230_DIRECT_DTB_ADDR, machine->fdt);
+
+ firmware_end_addr = riscv_find_and_load_firmware(machine, firmware_name,
+ &start_addr, NULL);
+ if (firmware_end_addr > K230_DIRECT_KERNEL_ADDR) {
+ error_report("K230 firmware overlaps kernel address 0x%x",
+ K230_DIRECT_KERNEL_ADDR);
+ exit(EXIT_FAILURE);
+ }
+
+ riscv_setup_rom_reset_vec(machine, &s->soc.c908_cpu, start_addr,
+ memmap[K230_DEV_BOOTROM].base,
+ memmap[K230_DEV_BOOTROM].size, kernel_entry,
+ K230_DIRECT_DTB_ADDR);
+}
+
+static void k230_firmware_boot(K230MachineState *s, MachineState *machine)
+{
+ const char *firmware_name = riscv_default_firmware_name(&s->soc.c908_cpu);
+ hwaddr start_addr = memmap[K230_DEV_DDRC].base;
+
+ if (machine->dtb || (machine->kernel_cmdline && *machine->kernel_cmdline)) {
+ error_report("K230 firmware boot does not support -dtb or -append");
+ exit(EXIT_FAILURE);
+ }
+
+ riscv_find_and_load_firmware(machine, firmware_name, &start_addr, NULL);
+
+ riscv_setup_rom_reset_vec(machine, &s->soc.c908_cpu, start_addr,
+ memmap[K230_DEV_BOOTROM].base,
+ memmap[K230_DEV_BOOTROM].size, 0, 0);
+}
+
+static void k230_machine_done(Notifier *notifier, void *data)
+{
+ K230MachineState *s = container_of(notifier, K230MachineState,
+ machine_done);
+ MachineState *machine = MACHINE(s);
+
+ if (machine->kernel_filename) {
+ k230_direct_boot(s, machine);
+ } else {
+ k230_firmware_boot(s, machine);
+ }
+}
+
+static void k230_machine_init(MachineState *machine)
+{
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
+ K230MachineState *s = RISCV_K230_MACHINE(machine);
+ MemoryRegion *sys_mem = get_system_memory();
+
+ if (machine->ram_size < mc->default_ram_size) {
+ char *sz = size_to_str(mc->default_ram_size);
+ error_report("Invalid RAM size, should be %s", sz);
+ g_free(sz);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Initialize SoC */
+ object_initialize_child(OBJECT(machine), "soc", &s->soc,
+ TYPE_RISCV_K230_SOC);
+ qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
+
+ /* Data Memory */
+ memory_region_add_subregion(sys_mem, memmap[K230_DEV_DDRC].base,
+ machine->ram);
+
+ s->machine_done.notify = k230_machine_done;
+ qemu_add_machine_init_done_notifier(&s->machine_done);
+}
+
+static void k230_machine_instance_init(Object *obj)
+{
+}
+
+static void k230_machine_class_init(ObjectClass *oc, const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->desc = "RISC-V Board compatible with Kendryte K230 SDK";
+ mc->init = k230_machine_init;
+ mc->default_cpus = 1;
+ mc->default_ram_id = "riscv.K230.ram"; /* DDR */
+ mc->default_ram_size = memmap[K230_DEV_DDRC].size;
+}
+
+static const TypeInfo k230_machine_typeinfo = {
+ .name = TYPE_RISCV_K230_MACHINE,
+ .parent = TYPE_MACHINE,
+ .class_init = k230_machine_class_init,
+ .instance_init = k230_machine_instance_init,
+ .instance_size = sizeof(K230MachineState),
+};
+
+static void k230_machine_init_register_types(void)
+{
+ type_register_static(&k230_machine_typeinfo);
+}
+
+type_init(k230_machine_init_register_types)
diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index 533472e22a..09cf855984 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -14,8 +14,8 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
-
riscv_ss.add(when: 'CONFIG_RISCV_MIPS_CPS', if_true: files('cps.c'))
riscv_ss.add(when: 'CONFIG_MIPS_BOSTON_AIA', if_true: files('boston-aia.c'))
+riscv_ss.add(when: 'CONFIG_K230', if_true: files('k230.c'))
hw_arch += {'riscv': riscv_ss}
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
new file mode 100644
index 0000000000..dcfde39096
--- /dev/null
+++ b/include/hw/riscv/k230.h
@@ -0,0 +1,145 @@
+/*
+ * QEMU RISC-V Virt Board Compatible with kendryte K230 SDK
+ *
+ * Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Provides a board compatible with the kendryte K230 SDK
+ *
+ * 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
+ *
+ * For more information, see <https://www.kendryte.com/en/proDetail/230>
+ */
+#ifndef HW_K230_H
+#define HW_K230_H
+
+#include "hw/core/boards.h"
+#include "hw/riscv/riscv_hart.h"
+
+#define C908_CPU_HARTID (0)
+
+#define TYPE_RISCV_K230_SOC "riscv.k230.soc"
+#define RISCV_K230_SOC(obj) \
+ OBJECT_CHECK(K230SoCState, (obj), TYPE_RISCV_K230_SOC)
+
+typedef struct K230SoCState {
+ /*< private >*/
+ DeviceState parent_obj;
+
+ /*< public >*/
+ RISCVHartArrayState c908_cpu; /* Small core */
+
+ MemoryRegion sram;
+ MemoryRegion bootrom;
+
+ DeviceState *c908_plic;
+} K230SoCState;
+
+#define TYPE_RISCV_K230_MACHINE MACHINE_TYPE_NAME("k230")
+#define RISCV_K230_MACHINE(obj) \
+ OBJECT_CHECK(K230MachineState, (obj), TYPE_RISCV_K230_MACHINE)
+
+typedef struct K230MachineState {
+ /*< private >*/
+ MachineState parent_obj;
+
+ /*< public >*/
+ K230SoCState soc;
+ Notifier machine_done;
+} K230MachineState;
+
+enum {
+ K230_DEV_DDRC,
+ K230_DEV_KPU_L2_CACHE,
+ K230_DEV_SRAM,
+ K230_DEV_KPU_CFG,
+ K230_DEV_FFT,
+ K230_DEV_AI_2D_ENGINE,
+ K230_DEV_GSDMA,
+ K230_DEV_DMA,
+ K230_DEV_DECOMP_GZIP,
+ K230_DEV_NON_AI_2D,
+ K230_DEV_ISP,
+ K230_DEV_DEWARP,
+ K230_DEV_RX_CSI,
+ K230_DEV_H264,
+ K230_DEV_2P5D,
+ K230_DEV_VO,
+ K230_DEV_VO_CFG,
+ K230_DEV_3D_ENGINE,
+ K230_DEV_PMU,
+ K230_DEV_RTC,
+ K230_DEV_CMU,
+ K230_DEV_RMU,
+ K230_DEV_BOOT,
+ K230_DEV_PWR,
+ K230_DEV_MAILBOX,
+ K230_DEV_IOMUX,
+ K230_DEV_TIMER,
+ K230_DEV_WDT0,
+ K230_DEV_WDT1,
+ K230_DEV_TS,
+ K230_DEV_HDI,
+ K230_DEV_STC,
+ K230_DEV_BOOTROM,
+ K230_DEV_SECURITY,
+ K230_DEV_UART0,
+ K230_DEV_UART1,
+ K230_DEV_UART2,
+ K230_DEV_UART3,
+ K230_DEV_UART4,
+ K230_DEV_I2C0,
+ K230_DEV_I2C1,
+ K230_DEV_I2C2,
+ K230_DEV_I2C3,
+ K230_DEV_I2C4,
+ K230_DEV_PWM,
+ K230_DEV_GPIO0,
+ K230_DEV_GPIO1,
+ K230_DEV_ADC,
+ K230_DEV_CODEC,
+ K230_DEV_I2S,
+ K230_DEV_USB0,
+ K230_DEV_USB1,
+ K230_DEV_SD0,
+ K230_DEV_SD1,
+ K230_DEV_QSPI0,
+ K230_DEV_QSPI1,
+ K230_DEV_SPI,
+ K230_DEV_HI_SYS_CFG,
+ K230_DEV_DDRC_CFG,
+ K230_DEV_FLASH,
+ K230_DEV_PLIC,
+ K230_DEV_CLINT,
+};
+
+enum {
+ /*
+ * K230 TRM v0.3.1 section 2.4 lists peripheral interrupt bits; SDK
+ * DTBs expose the corresponding PLIC IDs as bit + 16.
+ */
+ K230_UART0_IRQ = 16,
+ K230_UART1_IRQ = 17,
+ K230_UART2_IRQ = 18,
+ K230_UART3_IRQ = 19,
+ K230_UART4_IRQ = 20,
+};
+
+#define K230_UART_COUNT 5
+
+/*
+ * Integrates with the interrupt controller (PLIC),
+ * which can process 208 interrupt external sources
+ */
+#define K230_PLIC_NUM_SOURCES 208
+#define K230_PLIC_NUM_PRIORITIES 7
+#define K230_PLIC_PRIORITY_BASE 0x00
+#define K230_PLIC_PENDING_BASE 0x1000
+#define K230_PLIC_ENABLE_BASE 0x2000
+#define K230_PLIC_ENABLE_STRIDE 0x80
+#define K230_PLIC_CONTEXT_BASE 0x200000
+#define K230_PLIC_CONTEXT_STRIDE 0x1000
+
+#endif
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 3/5] hw/watchdog: add k230 watchdog initial support
2026-05-11 16:29 [PATCH v7 0/5] Add support for K230 board Chao Liu
2026-05-11 16:29 ` [PATCH v7 1/5] target/riscv: add thead-c908 cpu support Chao Liu
2026-05-11 16:29 ` [PATCH v7 2/5] hw/riscv: add k230 board initial support Chao Liu
@ 2026-05-11 16:29 ` Chao Liu
2026-05-11 16:29 ` [PATCH v7 4/5] tests/qtest: add test for K230 watchdog Chao Liu
` (2 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Chao Liu @ 2026-05-11 16:29 UTC (permalink / raw)
To: Chao Liu, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier
Cc: qemu-devel, qemu-riscv, Chao Liu, Mig Yang,
Daniel Henrique Barboza
From: Chao Liu <chao.liu@zevorn.cn>
Add programmable Watchdog Timer (WDT) peripheral for K230 machine.
Signed-off-by: Mig Yang <temashking@foxmail.com>
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
MAINTAINERS | 2 +
hw/riscv/Kconfig | 1 +
hw/riscv/k230.c | 18 ++
hw/watchdog/Kconfig | 4 +
hw/watchdog/k230_wdt.c | 296 +++++++++++++++++++++++++++++++++
hw/watchdog/meson.build | 1 +
hw/watchdog/trace-events | 9 +
include/hw/riscv/k230.h | 4 +
include/hw/watchdog/k230_wdt.h | 121 ++++++++++++++
9 files changed, 456 insertions(+)
create mode 100644 hw/watchdog/k230_wdt.c
create mode 100644 include/hw/watchdog/k230_wdt.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 196b476abe..e5ec6367ca 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1785,7 +1785,9 @@ M: Chao Liu <chao.liu.zevorn@gmail.com>
L: qemu-riscv@nongnu.org
S: Maintained
F: hw/riscv/k230.c
+F: hw/watchdog/k230_wdt.c
F: include/hw/riscv/k230.h
+F: include/hw/watchdog/k230_wdt.h
RX Machines
-----------
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index b1a7357866..5f8511cb92 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -144,3 +144,4 @@ config K230
select RISCV_IMSIC
select SERIAL_MM
select UNIMP
+ select K230_WDT
diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
index 327355b565..527d204fc8 100644
--- a/hw/riscv/k230.c
+++ b/hw/riscv/k230.c
@@ -107,6 +107,9 @@ static void k230_soc_init(Object *obj)
RISCVHartArrayState *cpu0 = &s->c908_cpu;
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);
+
qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
qdev_prop_set_uint64(DEVICE(cpu0), "resetvec",
@@ -187,6 +190,21 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
k230_create_uart(sys_mem, DEVICE(s->c908_plic), i);
}
+ /* Watchdog */
+ for (int i = 0; i < 2; i++) {
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) {
+ return;
+ }
+ }
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[0]), 0, memmap[K230_DEV_WDT0].base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[0]), 0,
+ qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT0_IRQ));
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[1]), 0, memmap[K230_DEV_WDT1].base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[1]), 0,
+ qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT1_IRQ));
+
/* unimplemented devices */
create_unimplemented_device("kpu.l2-cache",
memmap[K230_DEV_KPU_L2_CACHE].base,
diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
index 861fd00334..55f77c5c84 100644
--- a/hw/watchdog/Kconfig
+++ b/hw/watchdog/Kconfig
@@ -18,6 +18,10 @@ config WDT_DIAG288
config WDT_IMX2
bool
+config K230_WDT
+ bool
+ select PTIMER
+
config WDT_SBSA
bool
diff --git a/hw/watchdog/k230_wdt.c b/hw/watchdog/k230_wdt.c
new file mode 100644
index 0000000000..1f83b499e8
--- /dev/null
+++ b/hw/watchdog/k230_wdt.c
@@ -0,0 +1,296 @@
+/*
+ * K230 Watchdog Compatible with kendryte K230 SDK
+ *
+ * Copyright (c) 2025 Mig Yang <temashking@foxmail.com>
+ * Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Provides a board compatible with the kendryte K230 SDK
+ *
+ * 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
+ *
+ * For more information, see <https://www.kendryte.com/en/proDetail/230>
+ */
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/module.h"
+#include "system/watchdog.h"
+#include "migration/vmstate.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/watchdog/k230_wdt.h"
+#include "trace.h"
+
+static void k230_wdt_timeout(void *opaque)
+{
+ K230WdtState *s = K230_WDT(opaque);
+
+ trace_k230_wdt_timeout();
+
+ /* Set interrupt status if in interrupt mode */
+ if (s->cr & K230_WDT_CR_RMOD) {
+ s->stat |= K230_WDT_STAT_INT;
+ s->interrupt_pending = true;
+ qemu_set_irq(s->irq, 1);
+ trace_k230_wdt_interrupt();
+ } else {
+ /* Direct reset mode */
+ trace_k230_wdt_reset();
+ watchdog_perform_action();
+ }
+
+ /* Restart counter */
+ s->current_count = s->timeout_value;
+ ptimer_set_count(s->timer, s->current_count);
+ ptimer_run(s->timer, 1);
+}
+
+static void k230_wdt_reset(DeviceState *dev)
+{
+ K230WdtState *s = K230_WDT(dev);
+
+ trace_k230_wdt_reset_device();
+
+ ptimer_transaction_begin(s->timer);
+ ptimer_stop(s->timer);
+ ptimer_transaction_commit(s->timer);
+
+ /* Reset registers to default values */
+ s->cr = 0;
+ s->torr = 0;
+ s->ccvr = 0xFFFFFFFF;
+ s->stat = 0;
+ s->prot_level = 0x2;
+
+ s->interrupt_pending = false;
+ s->enabled = false;
+ s->timeout_value = 0;
+ s->current_count = 0xFFFFFFFF;
+}
+
+static uint64_t k230_wdt_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ K230WdtState *s = K230_WDT(opaque);
+ uint32_t value = 0;
+
+ switch (addr) {
+ case K230_WDT_CR:
+ value = s->cr;
+ break;
+ case K230_WDT_TORR:
+ value = s->torr;
+ break;
+ case K230_WDT_CCVR:
+ if (s->enabled) {
+ value = ptimer_get_count(s->timer);
+ } else {
+ value = s->current_count;
+ }
+ break;
+ case K230_WDT_STAT:
+ value = s->stat;
+ break;
+ case K230_WDT_PROT_LEVEL:
+ value = s->prot_level;
+ break;
+ case K230_WDT_COMP_PARAM_5:
+ value = 0; /* Upper limit of Timeout Period parameters */
+ break;
+ case K230_WDT_COMP_PARAM_4:
+ value = 0; /* Upper limit of Initial Timeout Period parameters */
+ break;
+ case K230_WDT_COMP_PARAM_3:
+ value = 0; /* Derived from WDT_TOP_RST parameter */
+ break;
+ case K230_WDT_COMP_PARAM_2:
+ value = 0xFFFFFFFF; /* Derived from WDT_RST_CNT parameter */
+ break;
+ case K230_WDT_COMP_PARAM_1:
+ /* Component parameters */
+ value = (32 << K230_WDT_CNT_WIDTH_SHIFT) | /* 32-bit counter */
+ (0 << K230_WDT_DFLT_TOP_INIT_SHIFT) |
+ (0 << K230_WDT_DFLT_TOP_SHIFT) |
+ (K230_WDT_RPL_16_CYCLES << K230_WDT_DFLT_RPL_SHIFT) |
+ (2 << K230_WDT_APB_DATA_WIDTH_SHIFT) | /* 32-bit APB */
+ K230_WDT_USE_FIX_TOP; /* Use fixed timeout values */
+ break;
+ case K230_WDT_COMP_VERSION:
+ value = K230_WDT_COMP_VERSION_VAL;
+ break;
+ case K230_WDT_COMP_TYPE:
+ value = K230_WDT_COMP_TYPE_VAL;
+ break;
+ default:
+ /* Other registers return 0 */
+ break;
+ }
+
+ trace_k230_wdt_read(addr, value);
+ return value;
+}
+
+static void k230_wdt_update_timer(K230WdtState *s)
+{
+ ptimer_transaction_begin(s->timer);
+
+ if (s->enabled && s->timeout_value > 0) {
+ ptimer_set_count(s->timer, s->current_count);
+ ptimer_run(s->timer, 1);
+ } else {
+ ptimer_stop(s->timer);
+ }
+
+ ptimer_transaction_commit(s->timer);
+}
+
+static uint32_t k230_wdt_calculate_timeout(uint32_t top_value)
+{
+ /* Calculate timeout based on TOP value */
+ /* For fixed timeout mode: 2^(16 + top_value) */
+ if (top_value <= 15) {
+ return 1 << (16 + top_value);
+ }
+ return 1 << 31; /* Maximum value for 32-bit counter */
+}
+
+static void k230_wdt_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned int size)
+{
+ K230WdtState *s = K230_WDT(opaque);
+
+ trace_k230_wdt_write(addr, value);
+
+ switch (addr) {
+ case K230_WDT_CR:
+ s->cr = value & (K230_WDT_CR_RPL_MASK << K230_WDT_CR_RPL_SHIFT |
+ K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
+
+ /* Update enabled state */
+ s->enabled = (s->cr & K230_WDT_CR_WDT_EN) != 0;
+
+ /* Update timer */
+ k230_wdt_update_timer(s);
+ break;
+
+ case K230_WDT_TORR:
+ s->torr = value & K230_WDT_TORR_TOP_MASK;
+
+ /* Calculate new timeout value */
+ s->timeout_value = k230_wdt_calculate_timeout(s->torr);
+ s->current_count = s->timeout_value;
+
+ /* Update timer if enabled */
+ if (s->enabled) {
+ k230_wdt_update_timer(s);
+ }
+ break;
+
+ case K230_WDT_CRR:
+ /* Restart counter with magic value 0x76 */
+ if ((value & 0xFF) == K230_WDT_CRR_RESTART) {
+ trace_k230_wdt_restart();
+ s->current_count = s->timeout_value;
+
+ /* Clear interrupt if pending */
+ if (s->interrupt_pending) {
+ s->stat &= ~K230_WDT_STAT_INT;
+ s->interrupt_pending = false;
+ qemu_set_irq(s->irq, 0);
+ }
+
+ /* Update timer */
+ k230_wdt_update_timer(s);
+ }
+ break;
+
+ case K230_WDT_EOI:
+ /* Clear interrupt */
+ s->stat &= ~K230_WDT_STAT_INT;
+ s->interrupt_pending = false;
+ qemu_set_irq(s->irq, 0);
+ break;
+
+ case K230_WDT_PROT_LEVEL:
+ s->prot_level = value & 0x7;
+ break;
+
+ default:
+ /* Read-only registers, ignore writes */
+ break;
+ }
+}
+
+static const MemoryRegionOps k230_wdt_ops = {
+ .read = k230_wdt_read,
+ .write = k230_wdt_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static const VMStateDescription vmstate_k230_wdt = {
+ .name = "k230.wdt",
+ .fields = (const VMStateField[]) {
+ VMSTATE_PTIMER(timer, K230WdtState),
+ VMSTATE_UINT32(cr, K230WdtState),
+ VMSTATE_UINT32(torr, K230WdtState),
+ VMSTATE_UINT32(ccvr, K230WdtState),
+ VMSTATE_UINT32(stat, K230WdtState),
+ VMSTATE_UINT32(prot_level, K230WdtState),
+ VMSTATE_BOOL(interrupt_pending, K230WdtState),
+ VMSTATE_BOOL(enabled, K230WdtState),
+ VMSTATE_UINT32(timeout_value, K230WdtState),
+ VMSTATE_UINT32(current_count, K230WdtState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void k230_wdt_realize(DeviceState *dev, Error **errp)
+{
+ K230WdtState *s = K230_WDT(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->mmio, OBJECT(dev),
+ &k230_wdt_ops, s,
+ TYPE_K230_WDT,
+ K230_WDT_MMIO_SIZE);
+ sysbus_init_mmio(sbd, &s->mmio);
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->timer = ptimer_init(k230_wdt_timeout, s,
+ PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
+ PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
+ PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
+
+ ptimer_transaction_begin(s->timer);
+ ptimer_set_freq(s->timer, K230_WDT_DEFAULT_FREQ);
+ ptimer_transaction_commit(s->timer);
+}
+
+static void k230_wdt_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = k230_wdt_realize;
+ device_class_set_legacy_reset(dc, k230_wdt_reset);
+ dc->vmsd = &vmstate_k230_wdt;
+ dc->desc = "K230 watchdog timer";
+ set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
+}
+
+static const TypeInfo k230_wdt_info = {
+ .name = TYPE_K230_WDT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(K230WdtState),
+ .class_init = k230_wdt_class_init,
+};
+
+static void k230_wdt_register_type(void)
+{
+ type_register_static(&k230_wdt_info);
+}
+type_init(k230_wdt_register_type)
diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
index 15370565bd..5edae65a36 100644
--- a/hw/watchdog/meson.build
+++ b/hw/watchdog/meson.build
@@ -6,5 +6,6 @@ system_ss.add(when: 'CONFIG_WDT_IB700', if_true: files('wdt_ib700.c'))
system_ss.add(when: 'CONFIG_WDT_DIAG288', if_true: files('wdt_diag288.c'))
system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('wdt_aspeed.c'))
system_ss.add(when: 'CONFIG_WDT_IMX2', if_true: files('wdt_imx2.c'))
+system_ss.add(when: 'CONFIG_K230_WDT', if_true: files('k230_wdt.c'))
system_ss.add(when: 'CONFIG_WDT_SBSA', if_true: files('sbsa_gwdt.c'))
specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_watchdog.c'))
diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
index ad3be1e9bd..d85b3ca769 100644
--- a/hw/watchdog/trace-events
+++ b/hw/watchdog/trace-events
@@ -33,3 +33,12 @@ spapr_watchdog_expired(uint64_t num, unsigned action) "num=%" PRIu64 " action=%u
# watchdog.c
watchdog_perform_action(unsigned int action) "action=%u"
watchdog_set_action(unsigned int action) "action=%u"
+
+# k230_wdt.c
+k230_wdt_read(uint64_t addr, uint32_t data) "K230 WDT read: [0x%" PRIx64 "] -> 0x%" PRIx32
+k230_wdt_write(uint64_t addr, uint64_t data) "K230 WDT write: [0x%" PRIx64 "] <- 0x%" PRIx64
+k230_wdt_timeout(void) "K230 WDT timeout"
+k230_wdt_interrupt(void) "K230 WDT interrupt"
+k230_wdt_reset(void) "K230 WDT system reset"
+k230_wdt_restart(void) "K230 WDT restart"
+k230_wdt_reset_device(void) "K230 WDT device reset"
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
index dcfde39096..592e1c26bf 100644
--- a/include/hw/riscv/k230.h
+++ b/include/hw/riscv/k230.h
@@ -17,6 +17,7 @@
#include "hw/core/boards.h"
#include "hw/riscv/riscv_hart.h"
+#include "hw/watchdog/k230_wdt.h"
#define C908_CPU_HARTID (0)
@@ -31,6 +32,7 @@ typedef struct K230SoCState {
/*< public >*/
RISCVHartArrayState c908_cpu; /* Small core */
+ K230WdtState wdt[2];
MemoryRegion sram;
MemoryRegion bootrom;
@@ -125,6 +127,8 @@ enum {
K230_UART2_IRQ = 18,
K230_UART3_IRQ = 19,
K230_UART4_IRQ = 20,
+ K230_WDT0_IRQ = 107,
+ K230_WDT1_IRQ = 108,
};
#define K230_UART_COUNT 5
diff --git a/include/hw/watchdog/k230_wdt.h b/include/hw/watchdog/k230_wdt.h
new file mode 100644
index 0000000000..ba194abebe
--- /dev/null
+++ b/include/hw/watchdog/k230_wdt.h
@@ -0,0 +1,121 @@
+/*
+ * K230 Watchdog Timer
+ *
+ * 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
+ *
+ * Copyright (c) 2025 Mig Yang <temashking@foxmail.com>
+ * Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef K230_WDT_H
+#define K230_WDT_H
+
+#include "qemu/bitops.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/irq.h"
+#include "hw/core/ptimer.h"
+#include "qom/object.h"
+
+#define TYPE_K230_WDT "riscv.k230.wdt"
+OBJECT_DECLARE_SIMPLE_TYPE(K230WdtState, K230_WDT)
+
+#define K230_WDT_DEFAULT_FREQ (32768)
+
+/* K230 Watchdog Register Map */
+enum K230WdtRegisters {
+ K230_WDT_CR = 0x00, /* Control Register */
+ K230_WDT_TORR = 0x04, /* Timeout Range Register */
+ K230_WDT_CCVR = 0x08, /* Current Counter Value Register */
+ K230_WDT_CRR = 0x0c, /* Counter Restart Register */
+ K230_WDT_STAT = 0x10, /* Interrupt Status Register */
+ K230_WDT_EOI = 0x14, /* Interrupt Clear Register */
+ K230_WDT_PROT_LEVEL = 0x1c, /* Protection Level Register */
+ K230_WDT_COMP_PARAM_5 = 0xe4, /* Component Parameters Register 5 */
+ K230_WDT_COMP_PARAM_4 = 0xe8, /* Component Parameters Register 4 */
+ K230_WDT_COMP_PARAM_3 = 0xec, /* Component Parameters Register 3 */
+ K230_WDT_COMP_PARAM_2 = 0xf0, /* Component Parameters Register 2 */
+ K230_WDT_COMP_PARAM_1 = 0xf4, /* Component Parameters Register 1 */
+ K230_WDT_COMP_VERSION = 0xf8, /* Component Version Register */
+ K230_WDT_COMP_TYPE = 0xfc, /* Component Type Register */
+};
+
+#define K230_WDT_MMIO_SIZE 0x100
+
+/* Control Register (WDT_CR) definitions */
+#define K230_WDT_CR_RPL_MASK 0x7 /* Reset Pulse Length */
+#define K230_WDT_CR_RPL_SHIFT 2
+#define K230_WDT_CR_RMOD BIT(1) /* Response Mode */
+#define K230_WDT_CR_WDT_EN BIT(0) /* Watchdog Enable */
+
+/* Reset Pulse Length values */
+#define K230_WDT_RPL_2_CYCLES 0x0
+#define K230_WDT_RPL_4_CYCLES 0x1
+#define K230_WDT_RPL_8_CYCLES 0x2
+#define K230_WDT_RPL_16_CYCLES 0x3
+#define K230_WDT_RPL_32_CYCLES 0x4
+#define K230_WDT_RPL_64_CYCLES 0x5
+#define K230_WDT_RPL_128_CYCLES 0x6
+#define K230_WDT_RPL_256_CYCLES 0x7
+
+/* Timeout Range Register (WDT_TORR) definitions */
+#define K230_WDT_TORR_TOP_MASK 0xf /* Timeout Period */
+
+/* Interrupt Status Register (WDT_STAT) definitions */
+#define K230_WDT_STAT_INT BIT(0) /* Interrupt Status */
+
+/* Counter Restart Register (WDT_CRR) magic value */
+#define K230_WDT_CRR_RESTART 0x76 /* Restart command */
+
+/* Component Parameters Register 1 (WDT_COMP_PARAM_1) definitions */
+#define K230_WDT_CNT_WIDTH_MASK 0x1f000000 /* Counter Width */
+#define K230_WDT_CNT_WIDTH_SHIFT 24
+#define K230_WDT_DFLT_TOP_INIT_MASK 0xf00000 /* Default Initial Timeout */
+#define K230_WDT_DFLT_TOP_INIT_SHIFT 20
+#define K230_WDT_DFLT_TOP_MASK 0xf0000 /* Default Timeout */
+#define K230_WDT_DFLT_TOP_SHIFT 16
+#define K230_WDT_DFLT_RPL_MASK 0x7 /* Default Reset Pulse Length */
+#define K230_WDT_DFLT_RPL_SHIFT 10
+#define K230_WDT_APB_DATA_WIDTH_MASK 0x3 /* APB Data Width */
+#define K230_WDT_APB_DATA_WIDTH_SHIFT 8
+#define K230_WDT_USE_FIX_TOP BIT(6) /* Use Fixed Timeout Values */
+#define K230_WDT_HC_TOP BIT(5) /* Hard-coded Timeout */
+#define K230_WDT_HC_RPL BIT(4) /* Hard-coded Reset Pulse Length */
+#define K230_WDT_HC_RMOD BIT(3) /* Hard-coded Response Mode */
+#define K230_WDT_DUAL_TOP BIT(2) /* Dual Timeout Period */
+#define K230_WDT_DFLT_RMOD BIT(1) /* Default Response Mode */
+#define K230_WDT_ALWAYS_EN BIT(0) /* Always Enabled */
+
+/* Component Type Register value */
+#define K230_WDT_COMP_TYPE_VAL 0x44570120
+
+/* Component Version Register value */
+#define K230_WDT_COMP_VERSION_VAL 0x3131302a /* "110*" */
+
+struct K230WdtState {
+ /* <private> */
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ MemoryRegion mmio;
+ qemu_irq irq;
+
+ struct ptimer_state *timer;
+
+ /* Register state */
+ uint32_t cr; /* Control Register */
+ uint32_t torr; /* Timeout Range Register */
+ uint32_t ccvr; /* Current Counter Value Register */
+ uint32_t stat; /* Interrupt Status Register */
+ uint32_t prot_level; /* Protection Level Register */
+
+ /* Internal state */
+ bool interrupt_pending;
+ bool enabled;
+ uint32_t timeout_value;
+ uint32_t current_count;
+};
+
+#endif /* K230_WDT_H */
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 4/5] tests/qtest: add test for K230 watchdog
2026-05-11 16:29 [PATCH v7 0/5] Add support for K230 board Chao Liu
` (2 preceding siblings ...)
2026-05-11 16:29 ` [PATCH v7 3/5] hw/watchdog: add k230 watchdog " Chao Liu
@ 2026-05-11 16:29 ` Chao Liu
2026-06-12 2:37 ` Alistair Francis
2026-05-11 16:29 ` [PATCH v7 5/5] docs/system/riscv: add documentation for k230 machine Chao Liu
2026-06-11 4:17 ` [PATCH v7 0/5] Add support for K230 board Alistair Francis
5 siblings, 1 reply; 11+ messages in thread
From: Chao Liu @ 2026-05-11 16:29 UTC (permalink / raw)
To: Chao Liu, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier
Cc: qemu-devel, qemu-riscv, Chao Liu, Mig Yang,
Daniel Henrique Barboza
From: Chao Liu <chao.liu@zevorn.cn>
Testing the Basic Functions of K230 WDT:
1. Reset Function
2. Timeout Check
3. Interrupt Function
Signed-off-by: Mig Yang <temashking@foxmail.com>
Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
Acked-by: Fabiano Rosas <farosas@suse.de>
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
MAINTAINERS | 1 +
tests/qtest/k230-wdt-test.c | 189 ++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 3 +-
3 files changed, 192 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/k230-wdt-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index e5ec6367ca..e7e3ed0c5c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1788,6 +1788,7 @@ F: hw/riscv/k230.c
F: hw/watchdog/k230_wdt.c
F: include/hw/riscv/k230.h
F: include/hw/watchdog/k230_wdt.h
+F: tests/qtest/k230-wdt-test.c
RX Machines
-----------
diff --git a/tests/qtest/k230-wdt-test.c b/tests/qtest/k230-wdt-test.c
new file mode 100644
index 0000000000..c8eaeaf1ae
--- /dev/null
+++ b/tests/qtest/k230-wdt-test.c
@@ -0,0 +1,189 @@
+/*
+ * QTest testcase for K230 Watchdog
+ *
+ * Copyright (c) 2025 Mig Yang <temashking@foxmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Provides a board compatible with the kendryte K230 SDK
+ *
+ * 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
+ *
+ * For more information, see <https://www.kendryte.com/en/proDetail/230>
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+#include "qemu/bitops.h"
+#include "libqtest.h"
+#include "hw/watchdog/k230_wdt.h"
+
+/* K230 WDT0 base address */
+#define K230_WDT0_BASE 0x91106000
+#define K230_WDT1_BASE 0x91106800
+
+/* Test WDT0 by default */
+#define WDT_BASE K230_WDT0_BASE
+
+static void test_register_read_write(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ /* Test Control Register (CR) read/write */
+ qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0xFFFFFFFF);
+ g_assert_cmphex(qtest_readl(qts, WDT_BASE + K230_WDT_CR), ==,
+ (K230_WDT_CR_RPL_MASK << K230_WDT_CR_RPL_SHIFT) |
+ K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
+
+ /* Test Timeout Range Register (TORR) read/write */
+ qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0xFFFFFFFF);
+ g_assert_cmphex(qtest_readl(qts, WDT_BASE + K230_WDT_TORR), ==,
+ K230_WDT_TORR_TOP_MASK);
+
+ /* Test Protection Level Register read/write */
+ qtest_writel(qts, WDT_BASE + K230_WDT_PROT_LEVEL, 0xFFFFFFFF);
+ g_assert_cmphex(qtest_readl(qts, WDT_BASE + K230_WDT_PROT_LEVEL), ==, 0x7);
+
+ qtest_quit(qts);
+}
+
+static void test_counter_restart(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ /* Enable watchdog and set timeout */
+ qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
+ qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0x5); /* TOP = 5 */
+
+ /* Read current counter value */
+ uint32_t initial_count = qtest_readl(qts, WDT_BASE + K230_WDT_CCVR);
+ g_assert_cmpuint(initial_count, >, 0);
+
+ /* Restart counter with magic value */
+ qtest_writel(qts, WDT_BASE + K230_WDT_CRR, K230_WDT_CRR_RESTART);
+
+ /* Wait for time */
+ qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 2);
+
+ /* Counter should be reset to timeout value */
+ uint32_t new_count = qtest_readl(qts, WDT_BASE + K230_WDT_CCVR);
+ g_assert_cmpuint(new_count, >, 0);
+ g_assert_cmpuint(new_count, !=, initial_count);
+
+ qtest_quit(qts);
+}
+
+static void test_interrupt_mode(void)
+{
+ QTestState *qts = qtest_init("-machine k230 --trace k230_*,file=k230.log");
+
+ /* Set interrupt mode and enable watchdog */
+ qtest_writel(qts, WDT_BASE + K230_WDT_CR,
+ K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
+ qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0x1); /* Short timeout */
+
+ /* Wait for timeout to trigger interrupt */
+ qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 10);
+
+ /* Check interrupt status */
+ uint32_t stat = qtest_readl(qts, WDT_BASE + K230_WDT_STAT);
+ g_assert_cmphex(stat & K230_WDT_STAT_INT, ==, K230_WDT_STAT_INT);
+
+ /* Clear interrupt */
+ qtest_writel(qts, WDT_BASE + K230_WDT_EOI, 0x1);
+ stat = qtest_readl(qts, WDT_BASE + K230_WDT_STAT);
+ g_assert_cmphex(stat & K230_WDT_STAT_INT, ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_reset_mode(void)
+{
+ QTestState *qts = qtest_init("-machine k230 -no-reboot");
+
+ /* Set reset mode and enable watchdog */
+ qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
+ qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0x1); /* Short timeout */
+
+ /* Wait for timeout to trigger reset */
+ qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 2);
+
+ /* In reset mode, the system should reset */
+ /* This test verifies that reset mode is properly configured */
+
+ qtest_quit(qts);
+}
+
+static void test_timeout_calculation(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ /* Test different timeout values */
+ for (uint32_t top = 0; top <= 15; top++) {
+ qtest_writel(qts, WDT_BASE + K230_WDT_TORR, top);
+ qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
+
+ /* Read current counter value */
+ uint32_t count = qtest_readl(qts, WDT_BASE + K230_WDT_CCVR);
+ g_assert_cmpuint(count, >, 0);
+
+ /* Disable watchdog for next iteration */
+ qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0);
+ }
+
+ qtest_quit(qts);
+}
+
+static void test_wdt1_registers(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ /* Test WDT1 registers (second watchdog) */
+ qtest_writel(qts, K230_WDT1_BASE + K230_WDT_CR, 0xFFFFFFFF);
+ g_assert_cmphex(qtest_readl(qts, K230_WDT1_BASE + K230_WDT_CR), ==,
+ (K230_WDT_CR_RPL_MASK << K230_WDT_CR_RPL_SHIFT) |
+ K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
+
+ qtest_writel(qts, K230_WDT1_BASE + K230_WDT_TORR, 0xFFFFFFFF);
+ g_assert_cmphex(qtest_readl(qts, K230_WDT1_BASE + K230_WDT_TORR), ==,
+ K230_WDT_TORR_TOP_MASK);
+
+ qtest_quit(qts);
+}
+
+static void test_enable_disable(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ /* Initially disabled */
+ uint32_t cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR);
+ g_assert_cmphex(cr & K230_WDT_CR_WDT_EN, ==, 0);
+
+ /* Enable watchdog */
+ qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
+ cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR);
+ g_assert_cmphex(cr & K230_WDT_CR_WDT_EN, ==, K230_WDT_CR_WDT_EN);
+
+ /* Disable watchdog */
+ qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0);
+ cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR);
+ g_assert_cmphex(cr & K230_WDT_CR_WDT_EN, ==, 0);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char *argv[])
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/k230-wdt/register_read_write", test_register_read_write);
+ qtest_add_func("/k230-wdt/counter_restart", test_counter_restart);
+ qtest_add_func("/k230-wdt/interrupt_mode", test_interrupt_mode);
+ qtest_add_func("/k230-wdt/reset_mode", test_reset_mode);
+ qtest_add_func("/k230-wdt/timeout_calculation", test_timeout_calculation);
+ qtest_add_func("/k230-wdt/wdt1_registers", test_wdt1_registers);
+ qtest_add_func("/k230-wdt/enable_disable", test_enable_disable);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 43f83ffd3a..45c4898454 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -290,7 +290,8 @@ qtests_riscv64 = ['riscv-csr-test'] + \
(unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
(config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
- ['iommu-riscv-test'] : [])
+ ['iommu-riscv-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
qos_test_ss = ss.source_set()
qos_test_ss.add(
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 5/5] docs/system/riscv: add documentation for k230 machine
2026-05-11 16:29 [PATCH v7 0/5] Add support for K230 board Chao Liu
` (3 preceding siblings ...)
2026-05-11 16:29 ` [PATCH v7 4/5] tests/qtest: add test for K230 watchdog Chao Liu
@ 2026-05-11 16:29 ` Chao Liu
2026-06-11 4:08 ` Alistair Francis
2026-06-11 4:17 ` [PATCH v7 0/5] Add support for K230 board Alistair Francis
5 siblings, 1 reply; 11+ messages in thread
From: Chao Liu @ 2026-05-11 16:29 UTC (permalink / raw)
To: Chao Liu, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier
Cc: qemu-devel, qemu-riscv
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
MAINTAINERS | 1 +
docs/system/riscv/k230.rst | 113 +++++++++++++++++++++++++++++++++++
docs/system/target-riscv.rst | 1 +
3 files changed, 115 insertions(+)
create mode 100644 docs/system/riscv/k230.rst
diff --git a/MAINTAINERS b/MAINTAINERS
index e7e3ed0c5c..6a752b1a0a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1784,6 +1784,7 @@ K230 Machines
M: Chao Liu <chao.liu.zevorn@gmail.com>
L: qemu-riscv@nongnu.org
S: Maintained
+F: docs/system/riscv/k230.rst
F: hw/riscv/k230.c
F: hw/watchdog/k230_wdt.c
F: include/hw/riscv/k230.h
diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst
new file mode 100644
index 0000000000..cea8202e55
--- /dev/null
+++ b/docs/system/riscv/k230.rst
@@ -0,0 +1,113 @@
+Kendryte K230 virt reference platform (``k230``)
+==========================================================================
+The ``k230`` machine is compatible with the Kendryte K230 SDK.
+
+The K230 is a chip from the AIoT SoC series made by Kendryte ® — a part of
+Canaan Inc. It uses a brand-new multi-heterogeneous unit accelerated computing
+structure.
+
+This chip has 2 RISC-V computing cores and a new-generation KPU (Knowledge
+Process Unit) smart computing unit.
+
+For more information, see <https://www.kendryte.com/en/proDetail/230>
+
+Supported devices
+-----------------
+The ``k230`` machine supports the following devices:
+
+* 1 c908 cores (little core)
+* Core Local Interruptor (CLINT)
+* Platform-Level Interrupt Controller (PLIC)
+* 2 K230 Watchdog Timer
+* 5 UART
+
+Boot options
+------------
+The ``k230`` machine supports K230 SDK boot through M-mode U-Boot, which then
+starts OpenSBI/Linux with ``bootm``. It also supports direct Linux boot.
+
+K230 SDK Linux kernels use T-HEAD C9xx private MAEE page table attributes. QEMU
+does not implement MAEE in the generic RISC-V MMU, so such kernels need to be
+built with standard RISC-V PTE bits before they can boot under QEMU.
+
+Running
+-------
+
+Direct Linux boot
+~~~~~~~~~~~~~~~~~
+
+This flow lets QEMU load OpenSBI, Linux, initrd, and DTB directly, without
+running SDK U-Boot. The Linux Image must be rebuilt with standard RISC-V PTE
+bits before running under QEMU.
+
+.. code-block:: bash
+
+ $ SDK=k230_sdk/output/k230_canmv_defconfig
+ $ qemu-system-riscv64 -machine k230 \
+ -kernel "$SDK/images/little-core/Image" \
+ -dtb "/tmp/user-k230-qemu.dtb" \
+ -initrd "$SDK/images/little-core/rootfs.cpio.gz" \
+ -append "console=ttyS0,115200 earlycon=sbi cma=0" \
+ -nographic
+
+Direct boot uses the SDK little-core RAM layout for OpenSBI at
+``0x08000000``, Linux at ``0x08200000``, and the DTB at ``0x0a000000``. The
+initrd is placed by QEMU's generic RISC-V boot helper, and QEMU writes the
+initrd range and kernel command line into ``/chosen``. The DTB passed with
+``-dtb`` should be derived from ``$SDK/images/little-core/k230.dtb`` and must
+describe that initrd location as usable memory and disable any devices that are
+not emulated by this machine.
+
+U-Boot boot
+~~~~~~~~~~~
+
+This flow starts SDK U-Boot in M-mode with ``-bios``. Until the SDK storage
+path is modeled, place OpenSBI, Linux, initrd, and DTB in RAM with loader
+devices and run ``bootm`` manually. The Linux Image must be rebuilt with
+standard RISC-V PTE bits before running under QEMU.
+
+.. code-block:: bash
+
+ $ SDK=k230_sdk/output/k230_canmv_defconfig
+ $ IMAGE=$SDK/images/little-core/Image
+ $ INITRD=$SDK/images/little-core/rootfs.cpio.gz
+ $ DTB=$SDK/images/little-core/k230.dtb
+ $ FWJUMP_UIMAGE=/tmp/k230-fw-jump.uImage
+ $ INITRD_END=$(printf "0x%x" $((0x0a100000 + $(stat -c %s "$INITRD"))))
+ $ "$SDK/little/buildroot-ext/host/bin/mkimage" \
+ -A riscv -O linux -T kernel -C none \
+ -a 0x8000000 -e 0x8000000 -n opensbi \
+ -d "$SDK/images/little-core/fw_jump.bin" "$FWJUMP_UIMAGE"
+ $ qemu-system-riscv64 -machine k230 \
+ -bios "$SDK/little/uboot/u-boot" \
+ -device loader,file="$FWJUMP_UIMAGE",addr=0xc100000,force-raw=on \
+ -device loader,file="$IMAGE",addr=0x8200000,force-raw=on \
+ -device loader,file="$INITRD",addr=0xa100000,force-raw=on \
+ -device loader,file="$DTB",addr=0xa000000,force-raw=on \
+ -nographic
+
+The loader addresses mirror the SDK ``k230_canmv_defconfig`` output. Read the
+U-Boot addresses from the generated environment, and read the Linux RAM base
+from the generated ``hw/k230.dts.txt``. This replaces the SDK storage and
+decompression steps.
+
+Press Enter to stop autoboot. At the U-Boot prompt, run these commands:
+
+.. code-block:: bash
+
+ K230# setenv bootargs console=ttyS0,115200 earlycon=sbi cma=0
+ K230# fdt addr 0xa000000
+ K230# fdt resize 8192
+ K230# fdt set /chosen linux,initrd-start <0x0 0xa100000>
+ K230# fdt set /chosen linux,initrd-end <0x0 ${INITRD_END}>
+ K230# fdt set /soc/sdhci0@91580000 status disabled
+ K230# fdt set /soc/sdhci1@91581000 status disabled
+ K230# bootm 0xc100000 - 0xa000000
+
+Use ``setenv`` so ``bootm`` writes the kernel command line into
+``/chosen/bootargs``. The ``fdt`` commands select the loaded DTB, add space for
+edits, describe the initrd range in ``/chosen``, and disable SDHCI nodes because
+this machine does not emulate those controllers yet. Replace ``${INITRD_END}``
+with the host-calculated value above when typing the command. ``cma=0`` avoids
+the SDK kernel reserving too much of the little-core memory window for initramfs
+boot.
diff --git a/docs/system/target-riscv.rst b/docs/system/target-riscv.rst
index 3ad5d1ddaf..b0b2f9584f 100644
--- a/docs/system/target-riscv.rst
+++ b/docs/system/target-riscv.rst
@@ -66,6 +66,7 @@ undocumented; you can get a complete list by running
.. toctree::
:maxdepth: 1
+ riscv/k230
riscv/microblaze-v-generic
riscv/microchip-icicle-kit
riscv/mips
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v7 2/5] hw/riscv: add k230 board initial support
2026-05-11 16:29 ` [PATCH v7 2/5] hw/riscv: add k230 board initial support Chao Liu
@ 2026-06-11 4:04 ` Alistair Francis
0 siblings, 0 replies; 11+ messages in thread
From: Alistair Francis @ 2026-06-11 4:04 UTC (permalink / raw)
To: Chao Liu
Cc: Pierrick Bouvier, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier, qemu-devel,
qemu-riscv, Peng Jiang
On Tue, May 12, 2026 at 2:32 AM Chao Liu <chao.liu.zevorn@gmail.com> wrote:
>
> K230 Board compatible with Kendryte K230 SDK.
>
> Preliminarily supports the C908 small core, which can run U-Boot and
> Linux kernels compiled by the K230 SDK.
>
> The K230 boot flow provides its device tree from firmware or software.
> QEMU does not generate a K230 DTB; users can pass one with -dtb for
> direct Linux boot, or rely on firmware/kernel built-in DTB for other
> payloads.
>
> Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
> Tested-by: Peng Jiang <3160104094@zju.edu.cn>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> MAINTAINERS | 7 +
> hw/riscv/Kconfig | 10 +
> hw/riscv/k230.c | 506 ++++++++++++++++++++++++++++++++++++++++
> hw/riscv/meson.build | 2 +-
> include/hw/riscv/k230.h | 145 ++++++++++++
> 5 files changed, 669 insertions(+), 1 deletion(-)
> create mode 100644 hw/riscv/k230.c
> create mode 100644 include/hw/riscv/k230.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f109e46172..196b476abe 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1780,6 +1780,13 @@ F: docs/system/riscv/xiangshan-kunminghu.rst
> F: hw/riscv/xiangshan_kmh.c
> F: include/hw/riscv/xiangshan_kmh.h
>
> +K230 Machines
> +M: Chao Liu <chao.liu.zevorn@gmail.com>
> +L: qemu-riscv@nongnu.org
> +S: Maintained
> +F: hw/riscv/k230.c
> +F: include/hw/riscv/k230.h
> +
> RX Machines
> -----------
> rx-gdbsim
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index 0222c93f87..b1a7357866 100644
> --- a/hw/riscv/Kconfig
> +++ b/hw/riscv/Kconfig
> @@ -134,3 +134,13 @@ config MIPS_BOSTON_AIA
> default y
> select PCI_EXPRESS
> select PCI_EXPRESS_XILINX
> +
> +config K230
> + bool
> + default y
> + depends on RISCV64
> + select RISCV_ACLINT
> + select RISCV_APLIC
> + select RISCV_IMSIC
> + select SERIAL_MM
> + select UNIMP
> diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
> new file mode 100644
> index 0000000000..327355b565
> --- /dev/null
> +++ b/hw/riscv/k230.c
> @@ -0,0 +1,506 @@
> +/*
> + * QEMU RISC-V Virt Board Compatible with Kendryte K230 SDK
> + *
> + * Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Provides a board compatible with the Kendryte K230 SDK
> + *
> + * 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
> + *
> + * For more information, see <https://www.kendryte.com/en/proDetail/230>
> + */
> +
> +#include "qemu/osdep.h"
> +#include "cpu-qom.h"
> +#include "qemu/cutils.h"
> +#include "qemu/error-report.h"
> +#include "qapi/error.h"
> +#include "system/device_tree.h"
> +#include "system/system.h"
> +#include "system/memory.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/core/loader.h"
> +#include "hw/core/sysbus.h"
> +#include "hw/riscv/k230.h"
> +#include "hw/riscv/boot.h"
> +#include "hw/intc/riscv_aclint.h"
> +#include "hw/intc/sifive_plic.h"
> +#include "hw/char/serial-mm.h"
> +#include "hw/misc/unimp.h"
> +
> +/* Align K230_SDK k230_canmv_defconfig */
> +#define K230_DIRECT_OPENSBI_ADDR 0x8000000
> +#define K230_DIRECT_KERNEL_ADDR 0x8200000
> +#define K230_DIRECT_DTB_ADDR 0xa000000
> +
> +static const MemMapEntry memmap[] = {
> + [K230_DEV_DDRC] = { 0x00000000, 0x80000000 },
> + [K230_DEV_KPU_L2_CACHE] = { 0x80000000, 0x00200000 },
> + [K230_DEV_SRAM] = { 0x80200000, 0x00200000 },
> + [K230_DEV_KPU_CFG] = { 0x80400000, 0x00000800 },
> + [K230_DEV_FFT] = { 0x80400800, 0x00000400 },
> + [K230_DEV_AI_2D_ENGINE] = { 0x80400C00, 0x00000800 },
> + [K230_DEV_GSDMA] = { 0x80800000, 0x00004000 },
> + [K230_DEV_DMA] = { 0x80804000, 0x00004000 },
> + [K230_DEV_DECOMP_GZIP] = { 0x80808000, 0x00004000 },
> + [K230_DEV_NON_AI_2D] = { 0x8080C000, 0x00004000 },
> + [K230_DEV_ISP] = { 0x90000000, 0x00008000 },
> + [K230_DEV_DEWARP] = { 0x90008000, 0x00001000 },
> + [K230_DEV_RX_CSI] = { 0x90009000, 0x00002000 },
> + [K230_DEV_H264] = { 0x90400000, 0x00010000 },
> + [K230_DEV_2P5D] = { 0x90800000, 0x00040000 },
> + [K230_DEV_VO] = { 0x90840000, 0x00010000 },
> + [K230_DEV_VO_CFG] = { 0x90850000, 0x00001000 },
> + [K230_DEV_3D_ENGINE] = { 0x90A00000, 0x00000800 },
> + [K230_DEV_PMU] = { 0x91000000, 0x00000C00 },
> + [K230_DEV_RTC] = { 0x91000C00, 0x00000400 },
> + [K230_DEV_CMU] = { 0x91100000, 0x00001000 },
> + [K230_DEV_RMU] = { 0x91101000, 0x00001000 },
> + [K230_DEV_BOOT] = { 0x91102000, 0x00001000 },
> + [K230_DEV_PWR] = { 0x91103000, 0x00001000 },
> + [K230_DEV_MAILBOX] = { 0x91104000, 0x00001000 },
> + [K230_DEV_IOMUX] = { 0x91105000, 0x00000800 },
> + [K230_DEV_TIMER] = { 0x91105800, 0x00000800 },
> + [K230_DEV_WDT0] = { 0x91106000, 0x00000800 },
> + [K230_DEV_WDT1] = { 0x91106800, 0x00000800 },
> + [K230_DEV_TS] = { 0x91107000, 0x00000800 },
> + [K230_DEV_HDI] = { 0x91107800, 0x00000800 },
> + [K230_DEV_STC] = { 0x91108000, 0x00000800 },
> + [K230_DEV_BOOTROM] = { 0x91200000, 0x00010000 },
> + [K230_DEV_SECURITY] = { 0x91210000, 0x00008000 },
> + [K230_DEV_UART0] = { 0x91400000, 0x00001000 },
> + [K230_DEV_UART1] = { 0x91401000, 0x00001000 },
> + [K230_DEV_UART2] = { 0x91402000, 0x00001000 },
> + [K230_DEV_UART3] = { 0x91403000, 0x00001000 },
> + [K230_DEV_UART4] = { 0x91404000, 0x00001000 },
> + [K230_DEV_I2C0] = { 0x91405000, 0x00001000 },
> + [K230_DEV_I2C1] = { 0x91406000, 0x00001000 },
> + [K230_DEV_I2C2] = { 0x91407000, 0x00001000 },
> + [K230_DEV_I2C3] = { 0x91408000, 0x00001000 },
> + [K230_DEV_I2C4] = { 0x91409000, 0x00001000 },
> + [K230_DEV_PWM] = { 0x9140A000, 0x00001000 },
> + [K230_DEV_GPIO0] = { 0x9140B000, 0x00001000 },
> + [K230_DEV_GPIO1] = { 0x9140C000, 0x00001000 },
> + [K230_DEV_ADC] = { 0x9140D000, 0x00001000 },
> + [K230_DEV_CODEC] = { 0x9140E000, 0x00001000 },
> + [K230_DEV_I2S] = { 0x9140F000, 0x00001000 },
> + [K230_DEV_USB0] = { 0x91500000, 0x00010000 },
> + [K230_DEV_USB1] = { 0x91540000, 0x00010000 },
> + [K230_DEV_SD0] = { 0x91580000, 0x00001000 },
> + [K230_DEV_SD1] = { 0x91581000, 0x00001000 },
> + [K230_DEV_QSPI0] = { 0x91582000, 0x00001000 },
> + [K230_DEV_QSPI1] = { 0x91583000, 0x00001000 },
> + [K230_DEV_SPI] = { 0x91584000, 0x00001000 },
> + [K230_DEV_HI_SYS_CFG] = { 0x91585000, 0x00000400 },
> + [K230_DEV_DDRC_CFG] = { 0x98000000, 0x02000000 },
> + [K230_DEV_FLASH] = { 0xC0000000, 0x08000000 },
> + [K230_DEV_PLIC] = { 0xF00000000, 0x00400000 },
> + [K230_DEV_CLINT] = { 0xF04000000, 0x00400000 },
> +};
> +
> +static void k230_soc_init(Object *obj)
> +{
> + K230SoCState *s = RISCV_K230_SOC(obj);
> + RISCVHartArrayState *cpu0 = &s->c908_cpu;
> +
> + object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY);
> + qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
> + qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
> + qdev_prop_set_uint64(DEVICE(cpu0), "resetvec",
> + memmap[K230_DEV_BOOTROM].base);
> +}
> +
> +static DeviceState *k230_create_plic(int base_hartid, int hartid_count)
> +{
> + g_autofree char *plic_hart_config = NULL;
> +
> + /* Per-socket PLIC hart topology configuration string */
> + plic_hart_config = riscv_plic_hart_config_string(hartid_count);
> +
> + /* Per-socket PLIC */
> + return sifive_plic_create(memmap[K230_DEV_PLIC].base,
> + plic_hart_config, hartid_count, base_hartid,
> + K230_PLIC_NUM_SOURCES,
> + K230_PLIC_NUM_PRIORITIES,
> + K230_PLIC_PRIORITY_BASE, K230_PLIC_PENDING_BASE,
> + K230_PLIC_ENABLE_BASE, K230_PLIC_ENABLE_STRIDE,
> + K230_PLIC_CONTEXT_BASE,
> + K230_PLIC_CONTEXT_STRIDE,
> + memmap[K230_DEV_PLIC].size);
> +}
> +
> +static void k230_create_uart(MemoryRegion *sys_mem, DeviceState *plic,
> + int index)
> +{
> + int uart_dev = K230_DEV_UART0 + index;
> + g_autofree char *name = g_strdup_printf("uart%d", index);
> +
> + /* Cover the non-16550 part of the SDK's 0x1000 UART window. */
> + create_unimplemented_device(name, memmap[uart_dev].base,
> + memmap[uart_dev].size);
> +
> + serial_mm_init(sys_mem, memmap[uart_dev].base, 2,
> + qdev_get_gpio_in(plic, K230_UART0_IRQ + index),
> + 399193, serial_hd(index), DEVICE_LITTLE_ENDIAN);
> +}
> +
> +static void k230_soc_realize(DeviceState *dev, Error **errp)
> +{
> + K230SoCState *s = RISCV_K230_SOC(dev);
> + MemoryRegion *sys_mem = get_system_memory();
> + int c908_cpus;
> +
> + sysbus_realize(SYS_BUS_DEVICE(&s->c908_cpu), &error_fatal);
> +
> + c908_cpus = s->c908_cpu.num_harts;
> +
> + /* SRAM */
> + memory_region_init_ram(&s->sram, OBJECT(dev), "sram",
> + memmap[K230_DEV_SRAM].size, &error_fatal);
> + memory_region_add_subregion(sys_mem, memmap[K230_DEV_SRAM].base,
> + &s->sram);
> +
> + /* BootROM */
> + memory_region_init_rom(&s->bootrom, OBJECT(dev), "bootrom",
> + memmap[K230_DEV_BOOTROM].size, &error_fatal);
> + memory_region_add_subregion(sys_mem, memmap[K230_DEV_BOOTROM].base,
> + &s->bootrom);
> +
> + /* PLIC */
> + s->c908_plic = k230_create_plic(C908_CPU_HARTID, c908_cpus);
> +
> + /* CLINT */
> + riscv_aclint_swi_create(memmap[K230_DEV_CLINT].base,
> + C908_CPU_HARTID, c908_cpus, false);
> + riscv_aclint_mtimer_create(memmap[K230_DEV_CLINT].base + 0x4000,
> + RISCV_ACLINT_DEFAULT_MTIMER_SIZE,
> + C908_CPU_HARTID, c908_cpus,
> + RISCV_ACLINT_DEFAULT_MTIMECMP,
> + RISCV_ACLINT_DEFAULT_MTIME,
> + RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ, true);
> +
> + /* UART */
> + for (int i = 0; i < K230_UART_COUNT; i++) {
> + k230_create_uart(sys_mem, DEVICE(s->c908_plic), i);
> + }
> +
> + /* unimplemented devices */
> + create_unimplemented_device("kpu.l2-cache",
> + memmap[K230_DEV_KPU_L2_CACHE].base,
> + memmap[K230_DEV_KPU_L2_CACHE].size);
> +
> + create_unimplemented_device("kpu_cfg", memmap[K230_DEV_KPU_CFG].base,
> + memmap[K230_DEV_KPU_CFG].size);
> +
> + create_unimplemented_device("fft", memmap[K230_DEV_FFT].base,
> + memmap[K230_DEV_FFT].size);
> +
> + create_unimplemented_device("2d-engine.ai",
> + memmap[K230_DEV_AI_2D_ENGINE].base,
> + memmap[K230_DEV_AI_2D_ENGINE].size);
> +
> + create_unimplemented_device("gsdma", memmap[K230_DEV_GSDMA].base,
> + memmap[K230_DEV_GSDMA].size);
> +
> + create_unimplemented_device("dma", memmap[K230_DEV_DMA].base,
> + memmap[K230_DEV_DMA].size);
> +
> + create_unimplemented_device("decomp-gzip",
> + memmap[K230_DEV_DECOMP_GZIP].base,
> + memmap[K230_DEV_DECOMP_GZIP].size);
> +
> + create_unimplemented_device("2d-engine.non-ai",
> + memmap[K230_DEV_NON_AI_2D].base,
> + memmap[K230_DEV_NON_AI_2D].size);
> +
> + create_unimplemented_device("isp", memmap[K230_DEV_ISP].base,
> + memmap[K230_DEV_ISP].size);
> +
> + create_unimplemented_device("dewarp", memmap[K230_DEV_DEWARP].base,
> + memmap[K230_DEV_DEWARP].size);
> +
> + create_unimplemented_device("rx-csi", memmap[K230_DEV_RX_CSI].base,
> + memmap[K230_DEV_RX_CSI].size);
> +
> + create_unimplemented_device("vpu", memmap[K230_DEV_H264].base,
> + memmap[K230_DEV_H264].size);
> +
> + create_unimplemented_device("gpu", memmap[K230_DEV_2P5D].base,
> + memmap[K230_DEV_2P5D].size);
> +
> + create_unimplemented_device("vo", memmap[K230_DEV_VO].base,
> + memmap[K230_DEV_VO].size);
> +
> + create_unimplemented_device("vo_cfg", memmap[K230_DEV_VO_CFG].base,
> + memmap[K230_DEV_VO_CFG].size);
> +
> + create_unimplemented_device("3d-engine", memmap[K230_DEV_3D_ENGINE].base,
> + memmap[K230_DEV_3D_ENGINE].size);
> +
> + create_unimplemented_device("pmu", memmap[K230_DEV_PMU].base,
> + memmap[K230_DEV_PMU].size);
> +
> + create_unimplemented_device("rtc", memmap[K230_DEV_RTC].base,
> + memmap[K230_DEV_RTC].size);
> +
> + 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);
> +
> + create_unimplemented_device("pwr", memmap[K230_DEV_PWR].base,
> + memmap[K230_DEV_PWR].size);
> +
> + create_unimplemented_device("ipcm", memmap[K230_DEV_MAILBOX].base,
> + memmap[K230_DEV_MAILBOX].size);
> +
> + create_unimplemented_device("iomux", memmap[K230_DEV_IOMUX].base,
> + memmap[K230_DEV_IOMUX].size);
> +
> + create_unimplemented_device("timer", memmap[K230_DEV_TIMER].base,
> + memmap[K230_DEV_TIMER].size);
> +
> + create_unimplemented_device("wdt0", memmap[K230_DEV_WDT0].base,
> + memmap[K230_DEV_WDT0].size);
> +
> + create_unimplemented_device("wdt1", memmap[K230_DEV_WDT1].base,
> + memmap[K230_DEV_WDT1].size);
> +
> + create_unimplemented_device("ts", memmap[K230_DEV_TS].base,
> + memmap[K230_DEV_TS].size);
> +
> + create_unimplemented_device("hdi", memmap[K230_DEV_HDI].base,
> + memmap[K230_DEV_HDI].size);
> +
> + create_unimplemented_device("stc", memmap[K230_DEV_STC].base,
> + memmap[K230_DEV_STC].size);
> +
> + create_unimplemented_device("security", memmap[K230_DEV_SECURITY].base,
> + memmap[K230_DEV_SECURITY].size);
> +
> + create_unimplemented_device("i2c0", memmap[K230_DEV_I2C0].base,
> + memmap[K230_DEV_I2C0].size);
> +
> + create_unimplemented_device("i2c1", memmap[K230_DEV_I2C1].base,
> + memmap[K230_DEV_I2C1].size);
> +
> + create_unimplemented_device("i2c2", memmap[K230_DEV_I2C2].base,
> + memmap[K230_DEV_I2C2].size);
> +
> + create_unimplemented_device("i2c3", memmap[K230_DEV_I2C3].base,
> + memmap[K230_DEV_I2C3].size);
> +
> + create_unimplemented_device("i2c4", memmap[K230_DEV_I2C4].base,
> + memmap[K230_DEV_I2C4].size);
> +
> + create_unimplemented_device("pwm", memmap[K230_DEV_PWM].base,
> + memmap[K230_DEV_PWM].size);
> +
> + create_unimplemented_device("gpio0", memmap[K230_DEV_GPIO0].base,
> + memmap[K230_DEV_GPIO0].size);
> +
> + create_unimplemented_device("gpio1", memmap[K230_DEV_GPIO1].base,
> + memmap[K230_DEV_GPIO1].size);
> +
> + create_unimplemented_device("adc", memmap[K230_DEV_ADC].base,
> + memmap[K230_DEV_ADC].size);
> +
> + create_unimplemented_device("codec", memmap[K230_DEV_CODEC].base,
> + memmap[K230_DEV_CODEC].size);
> +
> + create_unimplemented_device("i2s", memmap[K230_DEV_I2S].base,
> + memmap[K230_DEV_I2S].size);
> +
> + create_unimplemented_device("usb0", memmap[K230_DEV_USB0].base,
> + memmap[K230_DEV_USB0].size);
> +
> + create_unimplemented_device("usb1", memmap[K230_DEV_USB1].base,
> + memmap[K230_DEV_USB1].size);
> +
> + create_unimplemented_device("sd0", memmap[K230_DEV_SD0].base,
> + memmap[K230_DEV_SD0].size);
> +
> + create_unimplemented_device("sd1", memmap[K230_DEV_SD1].base,
> + memmap[K230_DEV_SD1].size);
> +
> + create_unimplemented_device("qspi0", memmap[K230_DEV_QSPI0].base,
> + memmap[K230_DEV_QSPI0].size);
> +
> + create_unimplemented_device("qspi1", memmap[K230_DEV_QSPI1].base,
> + memmap[K230_DEV_QSPI1].size);
> +
> + create_unimplemented_device("spi", memmap[K230_DEV_SPI].base,
> + memmap[K230_DEV_SPI].size);
> +
> + create_unimplemented_device("hi_sys_cfg", memmap[K230_DEV_HI_SYS_CFG].base,
> + memmap[K230_DEV_HI_SYS_CFG].size);
> +
> + create_unimplemented_device("ddrc_cfg", memmap[K230_DEV_DDRC_CFG].base,
> + memmap[K230_DEV_DDRC_CFG].size);
> +
> + create_unimplemented_device("flash", memmap[K230_DEV_FLASH].base,
> + memmap[K230_DEV_FLASH].size);
> +}
> +
> +static void k230_soc_class_init(ObjectClass *oc, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(oc);
> +
> + dc->realize = k230_soc_realize;
> +}
> +
> +static const TypeInfo k230_soc_type_info = {
> + .name = TYPE_RISCV_K230_SOC,
> + .parent = TYPE_DEVICE,
> + .instance_size = sizeof(K230SoCState),
> + .instance_init = k230_soc_init,
> + .class_init = k230_soc_class_init,
> +};
> +
> +static void k230_soc_register_types(void)
> +{
> + type_register_static(&k230_soc_type_info);
> +}
> +
> +type_init(k230_soc_register_types)
> +
> +static void k230_direct_boot(K230MachineState *s, MachineState *machine)
> +{
> + const char *firmware_name = riscv_default_firmware_name(&s->soc.c908_cpu);
> + RISCVBootInfo boot_info = {0};
> + hwaddr start_addr = K230_DIRECT_OPENSBI_ADDR;
> + hwaddr firmware_end_addr = 0;
> + hwaddr kernel_entry = 0;
> + int fdt_size = 0;
> +
> + if (machine->firmware && !strcmp(machine->firmware, "none")) {
> + error_report("K230 direct boot requires OpenSBI firmware; omit "
> + "-bios none or pass OpenSBI with -bios");
> + exit(EXIT_FAILURE);
> + }
> +
> + if (!machine->dtb) {
> + error_report("K230 direct boot requires -dtb");
> + exit(EXIT_FAILURE);
> + }
> +
> + machine->fdt = load_device_tree(machine->dtb, &fdt_size);
> + if (!machine->fdt) {
> + error_report("load_device_tree() failed");
> + exit(EXIT_FAILURE);
> + }
> +
> + qemu_fdt_add_path(machine->fdt, "/chosen");
> +
> + riscv_boot_info_init(&boot_info, &s->soc.c908_cpu);
> + riscv_load_kernel(machine, &boot_info, K230_DIRECT_KERNEL_ADDR, true, NULL);
> + kernel_entry = boot_info.image_low_addr;
> +
> + riscv_load_fdt(K230_DIRECT_DTB_ADDR, machine->fdt);
> +
> + firmware_end_addr = riscv_find_and_load_firmware(machine, firmware_name,
> + &start_addr, NULL);
> + if (firmware_end_addr > K230_DIRECT_KERNEL_ADDR) {
> + error_report("K230 firmware overlaps kernel address 0x%x",
> + K230_DIRECT_KERNEL_ADDR);
> + exit(EXIT_FAILURE);
> + }
> +
> + riscv_setup_rom_reset_vec(machine, &s->soc.c908_cpu, start_addr,
> + memmap[K230_DEV_BOOTROM].base,
> + memmap[K230_DEV_BOOTROM].size, kernel_entry,
> + K230_DIRECT_DTB_ADDR);
> +}
> +
> +static void k230_firmware_boot(K230MachineState *s, MachineState *machine)
> +{
> + const char *firmware_name = riscv_default_firmware_name(&s->soc.c908_cpu);
> + hwaddr start_addr = memmap[K230_DEV_DDRC].base;
> +
> + if (machine->dtb || (machine->kernel_cmdline && *machine->kernel_cmdline)) {
> + error_report("K230 firmware boot does not support -dtb or -append");
> + exit(EXIT_FAILURE);
> + }
> +
> + riscv_find_and_load_firmware(machine, firmware_name, &start_addr, NULL);
> +
> + riscv_setup_rom_reset_vec(machine, &s->soc.c908_cpu, start_addr,
> + memmap[K230_DEV_BOOTROM].base,
> + memmap[K230_DEV_BOOTROM].size, 0, 0);
> +}
> +
> +static void k230_machine_done(Notifier *notifier, void *data)
> +{
> + K230MachineState *s = container_of(notifier, K230MachineState,
> + machine_done);
> + MachineState *machine = MACHINE(s);
> +
> + if (machine->kernel_filename) {
> + k230_direct_boot(s, machine);
> + } else {
> + k230_firmware_boot(s, machine);
> + }
> +}
> +
> +static void k230_machine_init(MachineState *machine)
> +{
> + MachineClass *mc = MACHINE_GET_CLASS(machine);
> + K230MachineState *s = RISCV_K230_MACHINE(machine);
> + MemoryRegion *sys_mem = get_system_memory();
> +
> + if (machine->ram_size < mc->default_ram_size) {
> + char *sz = size_to_str(mc->default_ram_size);
> + error_report("Invalid RAM size, should be %s", sz);
> + g_free(sz);
> + exit(EXIT_FAILURE);
> + }
> +
> + /* Initialize SoC */
> + object_initialize_child(OBJECT(machine), "soc", &s->soc,
> + TYPE_RISCV_K230_SOC);
> + qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
> +
> + /* Data Memory */
> + memory_region_add_subregion(sys_mem, memmap[K230_DEV_DDRC].base,
> + machine->ram);
> +
> + s->machine_done.notify = k230_machine_done;
> + qemu_add_machine_init_done_notifier(&s->machine_done);
> +}
> +
> +static void k230_machine_instance_init(Object *obj)
> +{
> +}
> +
> +static void k230_machine_class_init(ObjectClass *oc, const void *data)
> +{
> + MachineClass *mc = MACHINE_CLASS(oc);
> +
> + mc->desc = "RISC-V Board compatible with Kendryte K230 SDK";
> + mc->init = k230_machine_init;
> + mc->default_cpus = 1;
> + mc->default_ram_id = "riscv.K230.ram"; /* DDR */
> + mc->default_ram_size = memmap[K230_DEV_DDRC].size;
> +}
> +
> +static const TypeInfo k230_machine_typeinfo = {
> + .name = TYPE_RISCV_K230_MACHINE,
> + .parent = TYPE_MACHINE,
> + .class_init = k230_machine_class_init,
> + .instance_init = k230_machine_instance_init,
> + .instance_size = sizeof(K230MachineState),
> +};
> +
> +static void k230_machine_init_register_types(void)
> +{
> + type_register_static(&k230_machine_typeinfo);
> +}
> +
> +type_init(k230_machine_init_register_types)
> diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
> index 533472e22a..09cf855984 100644
> --- a/hw/riscv/meson.build
> +++ b/hw/riscv/meson.build
> @@ -14,8 +14,8 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
> 'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
> riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
> riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
> -
> riscv_ss.add(when: 'CONFIG_RISCV_MIPS_CPS', if_true: files('cps.c'))
> riscv_ss.add(when: 'CONFIG_MIPS_BOSTON_AIA', if_true: files('boston-aia.c'))
> +riscv_ss.add(when: 'CONFIG_K230', if_true: files('k230.c'))
>
> hw_arch += {'riscv': riscv_ss}
> diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
> new file mode 100644
> index 0000000000..dcfde39096
> --- /dev/null
> +++ b/include/hw/riscv/k230.h
> @@ -0,0 +1,145 @@
> +/*
> + * QEMU RISC-V Virt Board Compatible with kendryte K230 SDK
> + *
> + * Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Provides a board compatible with the kendryte K230 SDK
> + *
> + * 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
> + *
> + * For more information, see <https://www.kendryte.com/en/proDetail/230>
> + */
> +#ifndef HW_K230_H
> +#define HW_K230_H
> +
> +#include "hw/core/boards.h"
> +#include "hw/riscv/riscv_hart.h"
> +
> +#define C908_CPU_HARTID (0)
> +
> +#define TYPE_RISCV_K230_SOC "riscv.k230.soc"
> +#define RISCV_K230_SOC(obj) \
> + OBJECT_CHECK(K230SoCState, (obj), TYPE_RISCV_K230_SOC)
> +
> +typedef struct K230SoCState {
> + /*< private >*/
> + DeviceState parent_obj;
> +
> + /*< public >*/
> + RISCVHartArrayState c908_cpu; /* Small core */
> +
> + MemoryRegion sram;
> + MemoryRegion bootrom;
> +
> + DeviceState *c908_plic;
> +} K230SoCState;
> +
> +#define TYPE_RISCV_K230_MACHINE MACHINE_TYPE_NAME("k230")
> +#define RISCV_K230_MACHINE(obj) \
> + OBJECT_CHECK(K230MachineState, (obj), TYPE_RISCV_K230_MACHINE)
> +
> +typedef struct K230MachineState {
> + /*< private >*/
> + MachineState parent_obj;
> +
> + /*< public >*/
> + K230SoCState soc;
> + Notifier machine_done;
> +} K230MachineState;
> +
> +enum {
> + K230_DEV_DDRC,
> + K230_DEV_KPU_L2_CACHE,
> + K230_DEV_SRAM,
> + K230_DEV_KPU_CFG,
> + K230_DEV_FFT,
> + K230_DEV_AI_2D_ENGINE,
> + K230_DEV_GSDMA,
> + K230_DEV_DMA,
> + K230_DEV_DECOMP_GZIP,
> + K230_DEV_NON_AI_2D,
> + K230_DEV_ISP,
> + K230_DEV_DEWARP,
> + K230_DEV_RX_CSI,
> + K230_DEV_H264,
> + K230_DEV_2P5D,
> + K230_DEV_VO,
> + K230_DEV_VO_CFG,
> + K230_DEV_3D_ENGINE,
> + K230_DEV_PMU,
> + K230_DEV_RTC,
> + K230_DEV_CMU,
> + K230_DEV_RMU,
> + K230_DEV_BOOT,
> + K230_DEV_PWR,
> + K230_DEV_MAILBOX,
> + K230_DEV_IOMUX,
> + K230_DEV_TIMER,
> + K230_DEV_WDT0,
> + K230_DEV_WDT1,
> + K230_DEV_TS,
> + K230_DEV_HDI,
> + K230_DEV_STC,
> + K230_DEV_BOOTROM,
> + K230_DEV_SECURITY,
> + K230_DEV_UART0,
> + K230_DEV_UART1,
> + K230_DEV_UART2,
> + K230_DEV_UART3,
> + K230_DEV_UART4,
> + K230_DEV_I2C0,
> + K230_DEV_I2C1,
> + K230_DEV_I2C2,
> + K230_DEV_I2C3,
> + K230_DEV_I2C4,
> + K230_DEV_PWM,
> + K230_DEV_GPIO0,
> + K230_DEV_GPIO1,
> + K230_DEV_ADC,
> + K230_DEV_CODEC,
> + K230_DEV_I2S,
> + K230_DEV_USB0,
> + K230_DEV_USB1,
> + K230_DEV_SD0,
> + K230_DEV_SD1,
> + K230_DEV_QSPI0,
> + K230_DEV_QSPI1,
> + K230_DEV_SPI,
> + K230_DEV_HI_SYS_CFG,
> + K230_DEV_DDRC_CFG,
> + K230_DEV_FLASH,
> + K230_DEV_PLIC,
> + K230_DEV_CLINT,
> +};
> +
> +enum {
> + /*
> + * K230 TRM v0.3.1 section 2.4 lists peripheral interrupt bits; SDK
> + * DTBs expose the corresponding PLIC IDs as bit + 16.
> + */
> + K230_UART0_IRQ = 16,
> + K230_UART1_IRQ = 17,
> + K230_UART2_IRQ = 18,
> + K230_UART3_IRQ = 19,
> + K230_UART4_IRQ = 20,
> +};
> +
> +#define K230_UART_COUNT 5
> +
> +/*
> + * Integrates with the interrupt controller (PLIC),
> + * which can process 208 interrupt external sources
> + */
> +#define K230_PLIC_NUM_SOURCES 208
> +#define K230_PLIC_NUM_PRIORITIES 7
> +#define K230_PLIC_PRIORITY_BASE 0x00
> +#define K230_PLIC_PENDING_BASE 0x1000
> +#define K230_PLIC_ENABLE_BASE 0x2000
> +#define K230_PLIC_ENABLE_STRIDE 0x80
> +#define K230_PLIC_CONTEXT_BASE 0x200000
> +#define K230_PLIC_CONTEXT_STRIDE 0x1000
> +
> +#endif
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v7 5/5] docs/system/riscv: add documentation for k230 machine
2026-05-11 16:29 ` [PATCH v7 5/5] docs/system/riscv: add documentation for k230 machine Chao Liu
@ 2026-06-11 4:08 ` Alistair Francis
0 siblings, 0 replies; 11+ messages in thread
From: Alistair Francis @ 2026-06-11 4:08 UTC (permalink / raw)
To: Chao Liu
Cc: Pierrick Bouvier, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier, qemu-devel,
qemu-riscv
On Tue, May 12, 2026 at 2:32 AM Chao Liu <chao.liu.zevorn@gmail.com> wrote:
>
> Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> MAINTAINERS | 1 +
> docs/system/riscv/k230.rst | 113 +++++++++++++++++++++++++++++++++++
> docs/system/target-riscv.rst | 1 +
> 3 files changed, 115 insertions(+)
> create mode 100644 docs/system/riscv/k230.rst
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e7e3ed0c5c..6a752b1a0a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1784,6 +1784,7 @@ K230 Machines
> M: Chao Liu <chao.liu.zevorn@gmail.com>
> L: qemu-riscv@nongnu.org
> S: Maintained
> +F: docs/system/riscv/k230.rst
> F: hw/riscv/k230.c
> F: hw/watchdog/k230_wdt.c
> F: include/hw/riscv/k230.h
> diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst
> new file mode 100644
> index 0000000000..cea8202e55
> --- /dev/null
> +++ b/docs/system/riscv/k230.rst
> @@ -0,0 +1,113 @@
> +Kendryte K230 virt reference platform (``k230``)
> +==========================================================================
> +The ``k230`` machine is compatible with the Kendryte K230 SDK.
> +
> +The K230 is a chip from the AIoT SoC series made by Kendryte ® — a part of
> +Canaan Inc. It uses a brand-new multi-heterogeneous unit accelerated computing
> +structure.
> +
> +This chip has 2 RISC-V computing cores and a new-generation KPU (Knowledge
> +Process Unit) smart computing unit.
> +
> +For more information, see <https://www.kendryte.com/en/proDetail/230>
> +
> +Supported devices
> +-----------------
> +The ``k230`` machine supports the following devices:
> +
> +* 1 c908 cores (little core)
> +* Core Local Interruptor (CLINT)
> +* Platform-Level Interrupt Controller (PLIC)
> +* 2 K230 Watchdog Timer
> +* 5 UART
> +
> +Boot options
> +------------
> +The ``k230`` machine supports K230 SDK boot through M-mode U-Boot, which then
> +starts OpenSBI/Linux with ``bootm``. It also supports direct Linux boot.
> +
> +K230 SDK Linux kernels use T-HEAD C9xx private MAEE page table attributes. QEMU
> +does not implement MAEE in the generic RISC-V MMU, so such kernels need to be
> +built with standard RISC-V PTE bits before they can boot under QEMU.
> +
> +Running
> +-------
> +
> +Direct Linux boot
> +~~~~~~~~~~~~~~~~~
> +
> +This flow lets QEMU load OpenSBI, Linux, initrd, and DTB directly, without
> +running SDK U-Boot. The Linux Image must be rebuilt with standard RISC-V PTE
> +bits before running under QEMU.
> +
> +.. code-block:: bash
> +
> + $ SDK=k230_sdk/output/k230_canmv_defconfig
> + $ qemu-system-riscv64 -machine k230 \
> + -kernel "$SDK/images/little-core/Image" \
> + -dtb "/tmp/user-k230-qemu.dtb" \
> + -initrd "$SDK/images/little-core/rootfs.cpio.gz" \
> + -append "console=ttyS0,115200 earlycon=sbi cma=0" \
> + -nographic
> +
> +Direct boot uses the SDK little-core RAM layout for OpenSBI at
> +``0x08000000``, Linux at ``0x08200000``, and the DTB at ``0x0a000000``. The
> +initrd is placed by QEMU's generic RISC-V boot helper, and QEMU writes the
> +initrd range and kernel command line into ``/chosen``. The DTB passed with
> +``-dtb`` should be derived from ``$SDK/images/little-core/k230.dtb`` and must
> +describe that initrd location as usable memory and disable any devices that are
> +not emulated by this machine.
> +
> +U-Boot boot
> +~~~~~~~~~~~
> +
> +This flow starts SDK U-Boot in M-mode with ``-bios``. Until the SDK storage
> +path is modeled, place OpenSBI, Linux, initrd, and DTB in RAM with loader
> +devices and run ``bootm`` manually. The Linux Image must be rebuilt with
> +standard RISC-V PTE bits before running under QEMU.
> +
> +.. code-block:: bash
> +
> + $ SDK=k230_sdk/output/k230_canmv_defconfig
> + $ IMAGE=$SDK/images/little-core/Image
> + $ INITRD=$SDK/images/little-core/rootfs.cpio.gz
> + $ DTB=$SDK/images/little-core/k230.dtb
> + $ FWJUMP_UIMAGE=/tmp/k230-fw-jump.uImage
> + $ INITRD_END=$(printf "0x%x" $((0x0a100000 + $(stat -c %s "$INITRD"))))
> + $ "$SDK/little/buildroot-ext/host/bin/mkimage" \
> + -A riscv -O linux -T kernel -C none \
> + -a 0x8000000 -e 0x8000000 -n opensbi \
> + -d "$SDK/images/little-core/fw_jump.bin" "$FWJUMP_UIMAGE"
> + $ qemu-system-riscv64 -machine k230 \
> + -bios "$SDK/little/uboot/u-boot" \
> + -device loader,file="$FWJUMP_UIMAGE",addr=0xc100000,force-raw=on \
> + -device loader,file="$IMAGE",addr=0x8200000,force-raw=on \
> + -device loader,file="$INITRD",addr=0xa100000,force-raw=on \
> + -device loader,file="$DTB",addr=0xa000000,force-raw=on \
> + -nographic
> +
> +The loader addresses mirror the SDK ``k230_canmv_defconfig`` output. Read the
> +U-Boot addresses from the generated environment, and read the Linux RAM base
> +from the generated ``hw/k230.dts.txt``. This replaces the SDK storage and
> +decompression steps.
> +
> +Press Enter to stop autoboot. At the U-Boot prompt, run these commands:
> +
> +.. code-block:: bash
> +
> + K230# setenv bootargs console=ttyS0,115200 earlycon=sbi cma=0
> + K230# fdt addr 0xa000000
> + K230# fdt resize 8192
> + K230# fdt set /chosen linux,initrd-start <0x0 0xa100000>
> + K230# fdt set /chosen linux,initrd-end <0x0 ${INITRD_END}>
> + K230# fdt set /soc/sdhci0@91580000 status disabled
> + K230# fdt set /soc/sdhci1@91581000 status disabled
> + K230# bootm 0xc100000 - 0xa000000
> +
> +Use ``setenv`` so ``bootm`` writes the kernel command line into
> +``/chosen/bootargs``. The ``fdt`` commands select the loaded DTB, add space for
> +edits, describe the initrd range in ``/chosen``, and disable SDHCI nodes because
> +this machine does not emulate those controllers yet. Replace ``${INITRD_END}``
> +with the host-calculated value above when typing the command. ``cma=0`` avoids
> +the SDK kernel reserving too much of the little-core memory window for initramfs
> +boot.
> diff --git a/docs/system/target-riscv.rst b/docs/system/target-riscv.rst
> index 3ad5d1ddaf..b0b2f9584f 100644
> --- a/docs/system/target-riscv.rst
> +++ b/docs/system/target-riscv.rst
> @@ -66,6 +66,7 @@ undocumented; you can get a complete list by running
> .. toctree::
> :maxdepth: 1
>
> + riscv/k230
> riscv/microblaze-v-generic
> riscv/microchip-icicle-kit
> riscv/mips
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v7 0/5] Add support for K230 board
2026-05-11 16:29 [PATCH v7 0/5] Add support for K230 board Chao Liu
` (4 preceding siblings ...)
2026-05-11 16:29 ` [PATCH v7 5/5] docs/system/riscv: add documentation for k230 machine Chao Liu
@ 2026-06-11 4:17 ` Alistair Francis
5 siblings, 0 replies; 11+ messages in thread
From: Alistair Francis @ 2026-06-11 4:17 UTC (permalink / raw)
To: Chao Liu
Cc: Pierrick Bouvier, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier, qemu-devel,
qemu-riscv
On Tue, May 12, 2026 at 2:32 AM Chao Liu <chao.liu.zevorn@gmail.com> wrote:
>
> This patch series adds support for U-Boot + OpenSBI + standard Linux kernel on
> K230 board. Thanks to Peng Jiang, Mig Yang, Renzao Ren, Yao Zi for their help.
>
> The current patchset fixes some bug and tag errors, Thanks to Alistair and
> Conor for the review.
>
> Test command with the direct Linux boot:
>
> ```
> $QEMU -M k230 \
> -kernel [Image] \
> -dtb [k230-qemu.dtb] \
> -initrd [rootfs.cpio.gz] \
> -nographic
> ```
>
> The k230-boot-assets repo [1] provides K230 Linux kernel images built with
> k230-sdk and Yocto.
>
> For more information, see docs/system/riscv/k230.rst.
>
> The GitLab CI result [2] passed all cases.
>
> PATCH v7 changelog:
> - Patchset: Removed invalid tags for patchset.
> - Patchset: Updated the K230 datasheet link in the patchset comment header.
> - Patch 2: Removed the unnecessary changes to MAINTAINERS.
> - Patch 2: Fixed an Oops caused by accesses to an unimpl UART MMIO address.
> - Patch 2: Adjusted the K230 machine hart count to 1.
> - Patch 2: Supported direct boot linux with `-kernel`.
> - Patch 3: Aligned the WDT interrupt number with the K230 datasheet.
> - Patch 5: Updated k230.rst Linux boot docs.
>
> PATCH v6 changelog:
> - Patchset: Rebased on the latest Alistair's riscv-to-apply.next branch [3].
> - Patch 4: Picked up Fabiano's Acked-by.
>
> PATCH v5 changelog:
> - Patchset: Rebased on Alistair's riscv-to-apply.next branch.
> - Patch 2: Fixed reset vector ROM jump to trap-handler bug.
>
> PATCH v4 changelog:
> - Patchset: Rebased on the latest master branch.
> - Patchset: No functional changes from v3.
>
> PATCH v3 changelog:
> - Patch 1: Align T-Head C908 CPU's RISC-V extension with XUANTIE-QEMU.
> - Patch 2: Adjust PLIC and CLINT addresses to match K230 datasheet.
>
> PATCH v2 changelog:
> - Patch 1: Add Svpbmt extension support for the T-Head C908 CPU.
> - Patch 2: Move the k230.rst definition from MAINTAINERS to Patch 5.
> - Patch 5: Apply Daniel's bugfix to build the k230 documentation successfully.
>
> PATCH v1 changelog:
> - Patch 1: Add T-Head C908 and C908v CPU support.
> - Patch 2: Add K230 board initial support(big core is not supported yet).
> - Patch 3: Add Programmable Watchdog Timer (WDT) peripheral support.
> - Patch 4: Add QEMU test for K230 watchdog.
> - Patch 5: Add documentation for K230 machine.
>
> ---
>
> Link:
> [1] https://github.com/zevorn/k230-boot-assets
> [2] https://gitlab.com/chao23.liu/qemu/-/pipelines/2515556858
> [3] https://github.com/alistair23/qemu/tree/riscv-to-apply.next
>
> Thanks,
> Chao
>
> Chao Liu (5):
> target/riscv: add thead-c908 cpu support
> hw/riscv: add k230 board initial support
> hw/watchdog: add k230 watchdog initial support
> tests/qtest: add test for K230 watchdog
> docs/system/riscv: add documentation for k230 machine
Thanks!
Applied to riscv-to-apply.next
Alistair
>
> MAINTAINERS | 11 +
> docs/system/riscv/k230.rst | 113 +++++++
> docs/system/target-riscv.rst | 1 +
> hw/riscv/Kconfig | 11 +
> hw/riscv/k230.c | 524 +++++++++++++++++++++++++++++++++
> hw/riscv/meson.build | 2 +-
> hw/watchdog/Kconfig | 4 +
> hw/watchdog/k230_wdt.c | 296 +++++++++++++++++++
> hw/watchdog/meson.build | 1 +
> hw/watchdog/trace-events | 9 +
> include/hw/riscv/k230.h | 149 ++++++++++
> include/hw/watchdog/k230_wdt.h | 121 ++++++++
> target/riscv/cpu-qom.h | 2 +
> target/riscv/cpu.c | 51 ++++
> target/riscv/th_csr.c | 380 +++++++++++++++++++++++-
> tests/qtest/k230-wdt-test.c | 189 ++++++++++++
> tests/qtest/meson.build | 3 +-
> 17 files changed, 1864 insertions(+), 3 deletions(-)
> create mode 100644 docs/system/riscv/k230.rst
> create mode 100644 hw/riscv/k230.c
> create mode 100644 hw/watchdog/k230_wdt.c
> create mode 100644 include/hw/riscv/k230.h
> create mode 100644 include/hw/watchdog/k230_wdt.h
> create mode 100644 tests/qtest/k230-wdt-test.c
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v7 4/5] tests/qtest: add test for K230 watchdog
2026-05-11 16:29 ` [PATCH v7 4/5] tests/qtest: add test for K230 watchdog Chao Liu
@ 2026-06-12 2:37 ` Alistair Francis
2026-06-12 5:36 ` Chao Liu
0 siblings, 1 reply; 11+ messages in thread
From: Alistair Francis @ 2026-06-12 2:37 UTC (permalink / raw)
To: Chao Liu
Cc: Pierrick Bouvier, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier, qemu-devel,
qemu-riscv, Chao Liu, Mig Yang, Daniel Henrique Barboza
On Tue, May 12, 2026 at 2:33 AM Chao Liu <chao.liu.zevorn@gmail.com> wrote:
>
> From: Chao Liu <chao.liu@zevorn.cn>
>
> Testing the Basic Functions of K230 WDT:
> 1. Reset Function
> 2. Timeout Check
> 3. Interrupt Function
>
> Signed-off-by: Mig Yang <temashking@foxmail.com>
> Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
> Acked-by: Fabiano Rosas <farosas@suse.de>
> Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
This fails `make check` for me. I guess the `k230` machine wasn't built?
541/542 qemu:qtest+qtest-aarch64 / qtest-aarch64/bios-tables-test
OK 91.02s 15 subtests passed
▶ 542/542 /riscv64/k230-wdt/register_read_write -
ERROR:../tests/qtest/libqtest.c:558:qtest_connect: assertion failed:
(s->fd >= 0 && s->qmp_fd >= 0) FAIL
▶ 542/542
ERROR
542/542 qemu:qtest+qtest-riscv64 / qtest-riscv64/k230-wdt-test
ERROR 51.60s killed by signal 6 SIGABRT
>>> PYTHON=/var/mnt/scratch/alistair/software/qemu/build/pyvenv/bin/python3 MSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 QTEST_QEMU_VNC_BINARY=./tools/qemu-vnc/qemu-vnc UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 MESON_TEST_ITERATION=1 QTEST_QEMU_IMG=./qemu-img RUST_BACKTRACE=1 G_TEST_DBUS_DAEMON=/var/mnt/scratch/alistair/software/qemu/tests/dbus-daemon.sh MALLOC_PERTURB_=94 QTEST_QEMU_STORAGE_DAEMON_BINARY=./storage-daemon/qemu-storage-daemon ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1 QTEST_QEMU_BINARY=./qemu-system-riscv64 /var/mnt/scratch/alistair/software/qemu/build/tests/qtest/k230-wdt-test --tap -k
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
✀ ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
stderr:
qemu-system-riscv64: unsupported machine type: "k230"
Use -machine help to list supported machines
socket_accept failed: Resource temporarily unavailable
**
ERROR:../tests/qtest/libqtest.c:558:qtest_connect: assertion failed:
(s->fd >= 0 && s->qmp_fd >= 0)
../tests/qtest/libqtest.c:201: kill_qemu() tried to terminate QEMU
process but encountered exit status 1 (expected 0)
(test program exited with status code -6)
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Summary of Failures:
542/542 qemu:qtest+qtest-riscv64 / qtest-riscv64/k230-wdt-test
ERROR 51.60s killed by signal 6 SIGABRT
Ok: 510
Expected Fail: 0
Fail: 1
Unexpected Pass: 0
Skipped: 31
Timeout: 0
Alistair
> ---
> MAINTAINERS | 1 +
> tests/qtest/k230-wdt-test.c | 189 ++++++++++++++++++++++++++++++++++++
> tests/qtest/meson.build | 3 +-
> 3 files changed, 192 insertions(+), 1 deletion(-)
> create mode 100644 tests/qtest/k230-wdt-test.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e5ec6367ca..e7e3ed0c5c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1788,6 +1788,7 @@ F: hw/riscv/k230.c
> F: hw/watchdog/k230_wdt.c
> F: include/hw/riscv/k230.h
> F: include/hw/watchdog/k230_wdt.h
> +F: tests/qtest/k230-wdt-test.c
>
> RX Machines
> -----------
> diff --git a/tests/qtest/k230-wdt-test.c b/tests/qtest/k230-wdt-test.c
> new file mode 100644
> index 0000000000..c8eaeaf1ae
> --- /dev/null
> +++ b/tests/qtest/k230-wdt-test.c
> @@ -0,0 +1,189 @@
> +/*
> + * QTest testcase for K230 Watchdog
> + *
> + * Copyright (c) 2025 Mig Yang <temashking@foxmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Provides a board compatible with the kendryte K230 SDK
> + *
> + * 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
> + *
> + * For more information, see <https://www.kendryte.com/en/proDetail/230>
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/timer.h"
> +#include "qemu/bitops.h"
> +#include "libqtest.h"
> +#include "hw/watchdog/k230_wdt.h"
> +
> +/* K230 WDT0 base address */
> +#define K230_WDT0_BASE 0x91106000
> +#define K230_WDT1_BASE 0x91106800
> +
> +/* Test WDT0 by default */
> +#define WDT_BASE K230_WDT0_BASE
> +
> +static void test_register_read_write(void)
> +{
> + QTestState *qts = qtest_init("-machine k230");
> +
> + /* Test Control Register (CR) read/write */
> + qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0xFFFFFFFF);
> + g_assert_cmphex(qtest_readl(qts, WDT_BASE + K230_WDT_CR), ==,
> + (K230_WDT_CR_RPL_MASK << K230_WDT_CR_RPL_SHIFT) |
> + K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
> +
> + /* Test Timeout Range Register (TORR) read/write */
> + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0xFFFFFFFF);
> + g_assert_cmphex(qtest_readl(qts, WDT_BASE + K230_WDT_TORR), ==,
> + K230_WDT_TORR_TOP_MASK);
> +
> + /* Test Protection Level Register read/write */
> + qtest_writel(qts, WDT_BASE + K230_WDT_PROT_LEVEL, 0xFFFFFFFF);
> + g_assert_cmphex(qtest_readl(qts, WDT_BASE + K230_WDT_PROT_LEVEL), ==, 0x7);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_counter_restart(void)
> +{
> + QTestState *qts = qtest_init("-machine k230");
> +
> + /* Enable watchdog and set timeout */
> + qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
> + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0x5); /* TOP = 5 */
> +
> + /* Read current counter value */
> + uint32_t initial_count = qtest_readl(qts, WDT_BASE + K230_WDT_CCVR);
> + g_assert_cmpuint(initial_count, >, 0);
> +
> + /* Restart counter with magic value */
> + qtest_writel(qts, WDT_BASE + K230_WDT_CRR, K230_WDT_CRR_RESTART);
> +
> + /* Wait for time */
> + qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 2);
> +
> + /* Counter should be reset to timeout value */
> + uint32_t new_count = qtest_readl(qts, WDT_BASE + K230_WDT_CCVR);
> + g_assert_cmpuint(new_count, >, 0);
> + g_assert_cmpuint(new_count, !=, initial_count);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_interrupt_mode(void)
> +{
> + QTestState *qts = qtest_init("-machine k230 --trace k230_*,file=k230.log");
> +
> + /* Set interrupt mode and enable watchdog */
> + qtest_writel(qts, WDT_BASE + K230_WDT_CR,
> + K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
> + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0x1); /* Short timeout */
> +
> + /* Wait for timeout to trigger interrupt */
> + qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 10);
> +
> + /* Check interrupt status */
> + uint32_t stat = qtest_readl(qts, WDT_BASE + K230_WDT_STAT);
> + g_assert_cmphex(stat & K230_WDT_STAT_INT, ==, K230_WDT_STAT_INT);
> +
> + /* Clear interrupt */
> + qtest_writel(qts, WDT_BASE + K230_WDT_EOI, 0x1);
> + stat = qtest_readl(qts, WDT_BASE + K230_WDT_STAT);
> + g_assert_cmphex(stat & K230_WDT_STAT_INT, ==, 0);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_reset_mode(void)
> +{
> + QTestState *qts = qtest_init("-machine k230 -no-reboot");
> +
> + /* Set reset mode and enable watchdog */
> + qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
> + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0x1); /* Short timeout */
> +
> + /* Wait for timeout to trigger reset */
> + qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 2);
> +
> + /* In reset mode, the system should reset */
> + /* This test verifies that reset mode is properly configured */
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_timeout_calculation(void)
> +{
> + QTestState *qts = qtest_init("-machine k230");
> +
> + /* Test different timeout values */
> + for (uint32_t top = 0; top <= 15; top++) {
> + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, top);
> + qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
> +
> + /* Read current counter value */
> + uint32_t count = qtest_readl(qts, WDT_BASE + K230_WDT_CCVR);
> + g_assert_cmpuint(count, >, 0);
> +
> + /* Disable watchdog for next iteration */
> + qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0);
> + }
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_wdt1_registers(void)
> +{
> + QTestState *qts = qtest_init("-machine k230");
> +
> + /* Test WDT1 registers (second watchdog) */
> + qtest_writel(qts, K230_WDT1_BASE + K230_WDT_CR, 0xFFFFFFFF);
> + g_assert_cmphex(qtest_readl(qts, K230_WDT1_BASE + K230_WDT_CR), ==,
> + (K230_WDT_CR_RPL_MASK << K230_WDT_CR_RPL_SHIFT) |
> + K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
> +
> + qtest_writel(qts, K230_WDT1_BASE + K230_WDT_TORR, 0xFFFFFFFF);
> + g_assert_cmphex(qtest_readl(qts, K230_WDT1_BASE + K230_WDT_TORR), ==,
> + K230_WDT_TORR_TOP_MASK);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_enable_disable(void)
> +{
> + QTestState *qts = qtest_init("-machine k230");
> +
> + /* Initially disabled */
> + uint32_t cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR);
> + g_assert_cmphex(cr & K230_WDT_CR_WDT_EN, ==, 0);
> +
> + /* Enable watchdog */
> + qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
> + cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR);
> + g_assert_cmphex(cr & K230_WDT_CR_WDT_EN, ==, K230_WDT_CR_WDT_EN);
> +
> + /* Disable watchdog */
> + qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0);
> + cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR);
> + g_assert_cmphex(cr & K230_WDT_CR_WDT_EN, ==, 0);
> +
> + qtest_quit(qts);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + g_test_init(&argc, &argv, NULL);
> +
> + qtest_add_func("/k230-wdt/register_read_write", test_register_read_write);
> + qtest_add_func("/k230-wdt/counter_restart", test_counter_restart);
> + qtest_add_func("/k230-wdt/interrupt_mode", test_interrupt_mode);
> + qtest_add_func("/k230-wdt/reset_mode", test_reset_mode);
> + qtest_add_func("/k230-wdt/timeout_calculation", test_timeout_calculation);
> + qtest_add_func("/k230-wdt/wdt1_registers", test_wdt1_registers);
> + qtest_add_func("/k230-wdt/enable_disable", test_enable_disable);
> +
> + return g_test_run();
> +}
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 43f83ffd3a..45c4898454 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -290,7 +290,8 @@ qtests_riscv64 = ['riscv-csr-test'] + \
> (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
> (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
> config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
> - ['iommu-riscv-test'] : [])
> + ['iommu-riscv-test'] : []) + \
> + (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
>
> qos_test_ss = ss.source_set()
> qos_test_ss.add(
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v7 4/5] tests/qtest: add test for K230 watchdog
2026-06-12 2:37 ` Alistair Francis
@ 2026-06-12 5:36 ` Chao Liu
0 siblings, 0 replies; 11+ messages in thread
From: Chao Liu @ 2026-06-12 5:36 UTC (permalink / raw)
To: Alistair Francis
Cc: Pierrick Bouvier, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Paolo Bonzini,
Christoph Muellner, Fabiano Rosas, Laurent Vivier, qemu-devel,
qemu-riscv, Chao Liu, Mig Yang, Daniel Henrique Barboza
On Fri, Jun 12, 2026 at 12:37:03PM +0800, Alistair Francis wrote:
> On Tue, May 12, 2026 at 2:33 AM Chao Liu <chao.liu.zevorn@gmail.com> wrote:
> >
> > From: Chao Liu <chao.liu@zevorn.cn>
> >
> > Testing the Basic Functions of K230 WDT:
> > 1. Reset Function
> > 2. Timeout Check
> > 3. Interrupt Function
> >
> > Signed-off-by: Mig Yang <temashking@foxmail.com>
> > Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
> > Acked-by: Fabiano Rosas <farosas@suse.de>
> > Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
>
> This fails `make check` for me. I guess the `k230` machine wasn't built?
>
I've tested the case on my local machine and it's running fine.
However, since the patch set is already a month old, maybe need rebase
next branch.
And I found an issue in the first patch while looking through the Milk-V
Duo patches.
https://lore.kernel.org/qemu-devel/agZFchak-jsc2moK@ZEVORN-PC.localdomain/
I'll fix it and send out a V8 version shortly. You can test it again
once that's out.
Thanks,
Chao
> 541/542 qemu:qtest+qtest-aarch64 / qtest-aarch64/bios-tables-test
> OK 91.02s 15 subtests passed
> ▶ 542/542 /riscv64/k230-wdt/register_read_write -
> ERROR:../tests/qtest/libqtest.c:558:qtest_connect: assertion failed:
> (s->fd >= 0 && s->qmp_fd >= 0) FAIL
> ▶ 542/542
> ERROR
> 542/542 qemu:qtest+qtest-riscv64 / qtest-riscv64/k230-wdt-test
> ERROR 51.60s killed by signal 6 SIGABRT
> >>> PYTHON=/var/mnt/scratch/alistair/software/qemu/build/pyvenv/bin/python3 MSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 QTEST_QEMU_VNC_BINARY=./tools/qemu-vnc/qemu-vnc UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 MESON_TEST_ITERATION=1 QTEST_QEMU_IMG=./qemu-img RUST_BACKTRACE=1 G_TEST_DBUS_DAEMON=/var/mnt/scratch/alistair/software/qemu/tests/dbus-daemon.sh MALLOC_PERTURB_=94 QTEST_QEMU_STORAGE_DAEMON_BINARY=./storage-daemon/qemu-storage-daemon ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1 QTEST_QEMU_BINARY=./qemu-system-riscv64 /var/mnt/scratch/alistair/software/qemu/build/tests/qtest/k230-wdt-test --tap -k
> ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
> ✀ ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
> stderr:
> qemu-system-riscv64: unsupported machine type: "k230"
> Use -machine help to list supported machines
> socket_accept failed: Resource temporarily unavailable
> **
> ERROR:../tests/qtest/libqtest.c:558:qtest_connect: assertion failed:
> (s->fd >= 0 && s->qmp_fd >= 0)
> ../tests/qtest/libqtest.c:201: kill_qemu() tried to terminate QEMU
> process but encountered exit status 1 (expected 0)
>
> (test program exited with status code -6)
> ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
>
>
> Summary of Failures:
>
> 542/542 qemu:qtest+qtest-riscv64 / qtest-riscv64/k230-wdt-test
> ERROR 51.60s killed by signal 6 SIGABRT
>
> Ok: 510
> Expected Fail: 0
> Fail: 1
> Unexpected Pass: 0
> Skipped: 31
> Timeout: 0
>
> Alistair
>
> > ---
> > MAINTAINERS | 1 +
> > tests/qtest/k230-wdt-test.c | 189 ++++++++++++++++++++++++++++++++++++
> > tests/qtest/meson.build | 3 +-
> > 3 files changed, 192 insertions(+), 1 deletion(-)
> > create mode 100644 tests/qtest/k230-wdt-test.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index e5ec6367ca..e7e3ed0c5c 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -1788,6 +1788,7 @@ F: hw/riscv/k230.c
> > F: hw/watchdog/k230_wdt.c
> > F: include/hw/riscv/k230.h
> > F: include/hw/watchdog/k230_wdt.h
> > +F: tests/qtest/k230-wdt-test.c
> >
> > RX Machines
> > -----------
> > diff --git a/tests/qtest/k230-wdt-test.c b/tests/qtest/k230-wdt-test.c
> > new file mode 100644
> > index 0000000000..c8eaeaf1ae
> > --- /dev/null
> > +++ b/tests/qtest/k230-wdt-test.c
> > @@ -0,0 +1,189 @@
> > +/*
> > + * QTest testcase for K230 Watchdog
> > + *
> > + * Copyright (c) 2025 Mig Yang <temashking@foxmail.com>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later
> > + *
> > + * Provides a board compatible with the kendryte K230 SDK
> > + *
> > + * 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
> > + *
> > + * For more information, see <https://www.kendryte.com/en/proDetail/230>
> > + */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qemu/timer.h"
> > +#include "qemu/bitops.h"
> > +#include "libqtest.h"
> > +#include "hw/watchdog/k230_wdt.h"
> > +
> > +/* K230 WDT0 base address */
> > +#define K230_WDT0_BASE 0x91106000
> > +#define K230_WDT1_BASE 0x91106800
> > +
> > +/* Test WDT0 by default */
> > +#define WDT_BASE K230_WDT0_BASE
> > +
> > +static void test_register_read_write(void)
> > +{
> > + QTestState *qts = qtest_init("-machine k230");
> > +
> > + /* Test Control Register (CR) read/write */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0xFFFFFFFF);
> > + g_assert_cmphex(qtest_readl(qts, WDT_BASE + K230_WDT_CR), ==,
> > + (K230_WDT_CR_RPL_MASK << K230_WDT_CR_RPL_SHIFT) |
> > + K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
> > +
> > + /* Test Timeout Range Register (TORR) read/write */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0xFFFFFFFF);
> > + g_assert_cmphex(qtest_readl(qts, WDT_BASE + K230_WDT_TORR), ==,
> > + K230_WDT_TORR_TOP_MASK);
> > +
> > + /* Test Protection Level Register read/write */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_PROT_LEVEL, 0xFFFFFFFF);
> > + g_assert_cmphex(qtest_readl(qts, WDT_BASE + K230_WDT_PROT_LEVEL), ==, 0x7);
> > +
> > + qtest_quit(qts);
> > +}
> > +
> > +static void test_counter_restart(void)
> > +{
> > + QTestState *qts = qtest_init("-machine k230");
> > +
> > + /* Enable watchdog and set timeout */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
> > + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0x5); /* TOP = 5 */
> > +
> > + /* Read current counter value */
> > + uint32_t initial_count = qtest_readl(qts, WDT_BASE + K230_WDT_CCVR);
> > + g_assert_cmpuint(initial_count, >, 0);
> > +
> > + /* Restart counter with magic value */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_CRR, K230_WDT_CRR_RESTART);
> > +
> > + /* Wait for time */
> > + qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 2);
> > +
> > + /* Counter should be reset to timeout value */
> > + uint32_t new_count = qtest_readl(qts, WDT_BASE + K230_WDT_CCVR);
> > + g_assert_cmpuint(new_count, >, 0);
> > + g_assert_cmpuint(new_count, !=, initial_count);
> > +
> > + qtest_quit(qts);
> > +}
> > +
> > +static void test_interrupt_mode(void)
> > +{
> > + QTestState *qts = qtest_init("-machine k230 --trace k230_*,file=k230.log");
> > +
> > + /* Set interrupt mode and enable watchdog */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_CR,
> > + K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
> > + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0x1); /* Short timeout */
> > +
> > + /* Wait for timeout to trigger interrupt */
> > + qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 10);
> > +
> > + /* Check interrupt status */
> > + uint32_t stat = qtest_readl(qts, WDT_BASE + K230_WDT_STAT);
> > + g_assert_cmphex(stat & K230_WDT_STAT_INT, ==, K230_WDT_STAT_INT);
> > +
> > + /* Clear interrupt */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_EOI, 0x1);
> > + stat = qtest_readl(qts, WDT_BASE + K230_WDT_STAT);
> > + g_assert_cmphex(stat & K230_WDT_STAT_INT, ==, 0);
> > +
> > + qtest_quit(qts);
> > +}
> > +
> > +static void test_reset_mode(void)
> > +{
> > + QTestState *qts = qtest_init("-machine k230 -no-reboot");
> > +
> > + /* Set reset mode and enable watchdog */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
> > + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, 0x1); /* Short timeout */
> > +
> > + /* Wait for timeout to trigger reset */
> > + qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 2);
> > +
> > + /* In reset mode, the system should reset */
> > + /* This test verifies that reset mode is properly configured */
> > +
> > + qtest_quit(qts);
> > +}
> > +
> > +static void test_timeout_calculation(void)
> > +{
> > + QTestState *qts = qtest_init("-machine k230");
> > +
> > + /* Test different timeout values */
> > + for (uint32_t top = 0; top <= 15; top++) {
> > + qtest_writel(qts, WDT_BASE + K230_WDT_TORR, top);
> > + qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
> > +
> > + /* Read current counter value */
> > + uint32_t count = qtest_readl(qts, WDT_BASE + K230_WDT_CCVR);
> > + g_assert_cmpuint(count, >, 0);
> > +
> > + /* Disable watchdog for next iteration */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0);
> > + }
> > +
> > + qtest_quit(qts);
> > +}
> > +
> > +static void test_wdt1_registers(void)
> > +{
> > + QTestState *qts = qtest_init("-machine k230");
> > +
> > + /* Test WDT1 registers (second watchdog) */
> > + qtest_writel(qts, K230_WDT1_BASE + K230_WDT_CR, 0xFFFFFFFF);
> > + g_assert_cmphex(qtest_readl(qts, K230_WDT1_BASE + K230_WDT_CR), ==,
> > + (K230_WDT_CR_RPL_MASK << K230_WDT_CR_RPL_SHIFT) |
> > + K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
> > +
> > + qtest_writel(qts, K230_WDT1_BASE + K230_WDT_TORR, 0xFFFFFFFF);
> > + g_assert_cmphex(qtest_readl(qts, K230_WDT1_BASE + K230_WDT_TORR), ==,
> > + K230_WDT_TORR_TOP_MASK);
> > +
> > + qtest_quit(qts);
> > +}
> > +
> > +static void test_enable_disable(void)
> > +{
> > + QTestState *qts = qtest_init("-machine k230");
> > +
> > + /* Initially disabled */
> > + uint32_t cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR);
> > + g_assert_cmphex(cr & K230_WDT_CR_WDT_EN, ==, 0);
> > +
> > + /* Enable watchdog */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN);
> > + cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR);
> > + g_assert_cmphex(cr & K230_WDT_CR_WDT_EN, ==, K230_WDT_CR_WDT_EN);
> > +
> > + /* Disable watchdog */
> > + qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0);
> > + cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR);
> > + g_assert_cmphex(cr & K230_WDT_CR_WDT_EN, ==, 0);
> > +
> > + qtest_quit(qts);
> > +}
> > +
> > +int main(int argc, char *argv[])
> > +{
> > + g_test_init(&argc, &argv, NULL);
> > +
> > + qtest_add_func("/k230-wdt/register_read_write", test_register_read_write);
> > + qtest_add_func("/k230-wdt/counter_restart", test_counter_restart);
> > + qtest_add_func("/k230-wdt/interrupt_mode", test_interrupt_mode);
> > + qtest_add_func("/k230-wdt/reset_mode", test_reset_mode);
> > + qtest_add_func("/k230-wdt/timeout_calculation", test_timeout_calculation);
> > + qtest_add_func("/k230-wdt/wdt1_registers", test_wdt1_registers);
> > + qtest_add_func("/k230-wdt/enable_disable", test_enable_disable);
> > +
> > + return g_test_run();
> > +}
> > diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> > index 43f83ffd3a..45c4898454 100644
> > --- a/tests/qtest/meson.build
> > +++ b/tests/qtest/meson.build
> > @@ -290,7 +290,8 @@ qtests_riscv64 = ['riscv-csr-test'] + \
> > (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
> > (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
> > config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
> > - ['iommu-riscv-test'] : [])
> > + ['iommu-riscv-test'] : []) + \
> > + (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
> >
> > qos_test_ss = ss.source_set()
> > qos_test_ss.add(
> >
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-06-12 5:36 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11 16:29 [PATCH v7 0/5] Add support for K230 board Chao Liu
2026-05-11 16:29 ` [PATCH v7 1/5] target/riscv: add thead-c908 cpu support Chao Liu
2026-05-11 16:29 ` [PATCH v7 2/5] hw/riscv: add k230 board initial support Chao Liu
2026-06-11 4:04 ` Alistair Francis
2026-05-11 16:29 ` [PATCH v7 3/5] hw/watchdog: add k230 watchdog " Chao Liu
2026-05-11 16:29 ` [PATCH v7 4/5] tests/qtest: add test for K230 watchdog Chao Liu
2026-06-12 2:37 ` Alistair Francis
2026-06-12 5:36 ` Chao Liu
2026-05-11 16:29 ` [PATCH v7 5/5] docs/system/riscv: add documentation for k230 machine Chao Liu
2026-06-11 4:08 ` Alistair Francis
2026-06-11 4:17 ` [PATCH v7 0/5] Add support for K230 board Alistair Francis
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.