* [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* 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
* [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* 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
* [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 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