All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC 1/1] hw/riscv/k230: add IOMUX register block model
@ 2026-07-10  4:18 Kangjie Huang
  2026-07-13  1:33 ` Alistair Francis
  2026-07-13  5:48 ` Chao Liu
  0 siblings, 2 replies; 4+ messages in thread
From: Kangjie Huang @ 2026-07-10  4:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kangjie Huang, Chao Liu, Pierrick Bouvier, Paolo Bonzini,
	Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Fabiano Rosas,
	Laurent Vivier, open list:K230 Machines

Add a sysbus model for the K230 IOMUX MMIO range at
0x91105000-0x911057ff. The model exposes the Function IO registers with
the reset defaults documented by the K230 Technical Reference Manual and
keeps 32-bit guest writes visible to firmware and the operating system.

This is enough for SDK U-Boot's pinctrl-single driver, which programs
the board pin configuration with 32-bit read-modify-write accesses.

Wire the device into the K230 SoC and add qtest coverage for reset
values, read/write storage and system reset.

Signed-off-by: Kangjie Huang <flamboyant.h.01@gmail.com>
---
RFC notes:
- This is my first QEMU upstream submission; feedback on patch structure
  and modelling boundaries would be appreciated.
- The model covers the IOMUX MMIO range 0x91105000-0x911057ff.
- It is intended to satisfy SDK U-Boot pinctrl-single register accesses.
- PMU IOMUX and physical pin routing are intentionally not modeled.
- Tested with qtest and SDK U-Boot smoke test.

 docs/system/riscv/k230.rst    |   1 +
 hw/misc/Kconfig               |   3 +
 hw/misc/k230_iomux.c          | 138 ++++++++++++++++++++++++++++++++++
 hw/misc/meson.build           |   2 +
 hw/misc/trace-events          |   4 +
 hw/riscv/Kconfig              |   1 +
 hw/riscv/k230.c               |  11 ++-
 include/hw/misc/k230_iomux.h  |  34 +++++++++
 include/hw/riscv/k230.h       |   2 +
 tests/qtest/k230-iomux-test.c | 107 ++++++++++++++++++++++++++
 tests/qtest/meson.build       |   5 +-
 11 files changed, 304 insertions(+), 4 deletions(-)
 create mode 100644 hw/misc/k230_iomux.c
 create mode 100644 include/hw/misc/k230_iomux.h
 create mode 100644 tests/qtest/k230-iomux-test.c

diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst
index cea8202e55..b160c401fd 100644
--- a/docs/system/riscv/k230.rst
+++ b/docs/system/riscv/k230.rst
@@ -20,6 +20,7 @@ The ``k230`` machine supports the following devices:
 * Platform-Level Interrupt Controller (PLIC)
 * 2 K230 Watchdog Timer
 * 5 UART
+* K230 IOMUX register block
 
 Boot options
 ------------
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6653..573d24a9df 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -257,4 +257,7 @@ config XLNX_VERSAL_TRNG
 config XLNX_ZYNQ_DDRC
     bool
 
+config K230_IOMUX
+    bool
+
 source macio/Kconfig
diff --git a/hw/misc/k230_iomux.c b/hw/misc/k230_iomux.c
new file mode 100644
index 0000000000..5f5e2aac51
--- /dev/null
+++ b/hw/misc/k230_iomux.c
@@ -0,0 +1,138 @@
+/*
+ * K230 IOMUX register block compatible with Kendryte K230 SDK
+ *
+ * Copyright (c) 2026 Kangjie Huang <flamboyant.h.01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Provides the low IOMUX register block at 0x91105000 with the reset
+ * defaults documented by the K230 Technical Reference Manual. The model keeps
+ * guest 32-bit register writes visible to firmware and the operating system,
+ * but does not model physical pin routing, pad electrical effects, or the
+ * separate PMU IOMUX block.
+ *
+ * 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 "hw/misc/k230_iomux.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "trace.h"
+
+/* TRM V0.3.1 section 12.9.2.1 Function IO register list. */
+static const uint32_t k230_iomux_reset_values[] = {
+    0x944, 0x944, 0x929, 0x908, 0x888, 0x908, 0x948, 0x890,
+    0x890, 0x890, 0x910, 0x890, 0x890, 0x8a9, 0xa9e, 0xabf,
+    0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
+    0xb1e, 0xa90, 0xabf, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
+    0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0x890, 0x910,
+    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x890, 0x910,
+    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x89e, 0x89f,
+    0x99e, 0x99e, 0x99e, 0x99e, 0x890, 0x890, 0x8a9, 0x8a9,
+};
+
+static void k230_iomux_reset(DeviceState *dev)
+{
+    K230IomuxState *s = K230_IOMUX(dev);
+
+    memset(s->regs, 0, sizeof(s->regs));
+    memcpy(s->regs, k230_iomux_reset_values,
+           sizeof(k230_iomux_reset_values));
+}
+
+static uint64_t k230_iomux_read(void *opaque, hwaddr offset, unsigned size)
+{
+    K230IomuxState *s = K230_IOMUX(opaque);
+    uint32_t value;
+
+    if (offset + size > K230_IOMUX_MMIO_SIZE || (offset & 3)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid access offset 0x%" HWADDR_PRIx
+                      " size %u\n", __func__, offset, size);
+        return 0;
+    }
+
+    value = s->regs[offset >> 2];
+    trace_k230_iomux_read(offset, value);
+    return value;
+}
+
+static void k230_iomux_write(void *opaque, hwaddr offset,
+                             uint64_t value, unsigned size)
+{
+    K230IomuxState *s = K230_IOMUX(opaque);
+
+    if (offset + size > K230_IOMUX_MMIO_SIZE || (offset & 3)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid access offset 0x%" HWADDR_PRIx
+                      " size %u\n", __func__, offset, size);
+        return;
+    }
+
+    trace_k230_iomux_write(offset, value);
+    s->regs[offset >> 2] = value;
+}
+
+static const MemoryRegionOps k230_iomux_ops = {
+    .read = k230_iomux_read,
+    .write = k230_iomux_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void k230_iomux_init(Object *obj)
+{
+    K230IomuxState *s = K230_IOMUX(obj);
+
+    memory_region_init_io(&s->mmio, obj, &k230_iomux_ops, s,
+                          TYPE_K230_IOMUX, K230_IOMUX_MMIO_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static const VMStateDescription vmstate_k230_iomux = {
+    .name = "k230.iomux",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, K230IomuxState, K230_IOMUX_NUM_REGS),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void k230_iomux_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->desc = "Kendryte K230 IOMUX";
+    dc->vmsd = &vmstate_k230_iomux;
+    device_class_set_legacy_reset(dc, k230_iomux_reset);
+}
+
+static const TypeInfo k230_iomux_info = {
+    .name          = TYPE_K230_IOMUX,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(K230IomuxState),
+    .instance_init = k230_iomux_init,
+    .class_init    = k230_iomux_class_init,
+};
+
+static void k230_iomux_register_types(void)
+{
+    type_register_static(&k230_iomux_info);
+}
+
+type_init(k230_iomux_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6035..31e5145a35 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -168,3 +168,5 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
 
 # HPPA devices
 system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
+
+system_ss.add(when: 'CONFIG_K230_IOMUX', if_true: files('k230_iomux.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c9a868b3ef..388967c9e1 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -405,6 +405,10 @@ djmemc_write(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRI
 iosb_read(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u"
 iosb_write(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u"
 
+# k230_iomux.c
+k230_iomux_read(uint64_t offset, uint32_t value) "offset=0x%" PRIx64 " value=0x%" PRIx32
+k230_iomux_write(uint64_t offset, uint64_t value) "offset=0x%" PRIx64 " value=0x%" PRIx64
+
 # aspeed_sli.c
 aspeed_sli_write(uint64_t offset, unsigned int size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32
 aspeed_sli_read(uint64_t offset, unsigned int size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index de37c08cae..4c9fc3e7ad 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -162,3 +162,4 @@ config K230
     select SERIAL_MM
     select UNIMP
     select K230_WDT
+    select K230_IOMUX
diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
index 656f28190c..430c1646b5 100644
--- a/hw/riscv/k230.c
+++ b/hw/riscv/k230.c
@@ -110,6 +110,7 @@ static void k230_soc_init(Object *obj)
     object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY);
     object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT);
     object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT);
+    object_initialize_child(obj, "k230-iomux", &s->iomux, TYPE_K230_IOMUX);
 
     qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
     qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
@@ -198,6 +199,11 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
         }
     }
 
+    /* IOMUX */
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->iomux), 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));
@@ -206,6 +212,8 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
     sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[1]), 0,
                        qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT1_IRQ));
 
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->iomux), 0, memmap[K230_DEV_IOMUX].base);
+
     /* unimplemented devices */
     create_unimplemented_device("kpu.l2-cache",
                                 memmap[K230_DEV_KPU_L2_CACHE].base,
@@ -280,9 +288,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
     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);
 
diff --git a/include/hw/misc/k230_iomux.h b/include/hw/misc/k230_iomux.h
new file mode 100644
index 0000000000..15e4bb339d
--- /dev/null
+++ b/include/hw/misc/k230_iomux.h
@@ -0,0 +1,34 @@
+/*
+ * Kendryte K230 IOMUX register block
+ *
+ * This models the low IOMUX register block. Physical pin routing, pad
+ * electrical effects, and the separate PMU IOMUX block are out of scope.
+ *
+ * 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) 2026 Kangjie Huang <flamboyant.h.01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_K230_IOMUX_H
+#define HW_MISC_K230_IOMUX_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_K230_IOMUX "riscv.k230.iomux"
+OBJECT_DECLARE_SIMPLE_TYPE(K230IomuxState, K230_IOMUX)
+
+#define K230_IOMUX_MMIO_SIZE 0x800
+#define K230_IOMUX_NUM_REGS (K230_IOMUX_MMIO_SIZE / sizeof(uint32_t))
+
+struct K230IomuxState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+    uint32_t regs[K230_IOMUX_NUM_REGS];
+};
+
+#endif /* HW_MISC_K230_IOMUX_H */
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
index 592e1c26bf..223e76b420 100644
--- a/include/hw/riscv/k230.h
+++ b/include/hw/riscv/k230.h
@@ -18,6 +18,7 @@
 #include "hw/core/boards.h"
 #include "hw/riscv/riscv_hart.h"
 #include "hw/watchdog/k230_wdt.h"
+#include "hw/misc/k230_iomux.h"
 
 #define C908_CPU_HARTID   (0)
 
@@ -33,6 +34,7 @@ typedef struct K230SoCState {
     RISCVHartArrayState c908_cpu; /* Small core */
 
     K230WdtState wdt[2];
+    K230IomuxState iomux;
     MemoryRegion sram;
     MemoryRegion bootrom;
 
diff --git a/tests/qtest/k230-iomux-test.c b/tests/qtest/k230-iomux-test.c
new file mode 100644
index 0000000000..ca2d557955
--- /dev/null
+++ b/tests/qtest/k230-iomux-test.c
@@ -0,0 +1,107 @@
+/*
+ * QTest testcase for K230 IOMUX
+ *
+ * Copyright (c) 2026 Kangjie Huang <flamboyant.h.01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Provides test coverage for the low IOMUX register block 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 "libqtest.h"
+#include "hw/misc/k230_iomux.h"
+
+#define K230_IOMUX_BASE 0x91105000
+#define K230_IOMUX_IO0  (K230_IOMUX_BASE + 0x00)
+#define K230_IOMUX_IO1  (K230_IOMUX_BASE + 0x04)
+#define K230_IOMUX_LAST (K230_IOMUX_BASE + K230_IOMUX_MMIO_SIZE - 4)
+
+static const uint32_t k230_iomux_reset_values[] = {
+    0x944, 0x944, 0x929, 0x908, 0x888, 0x908, 0x948, 0x890,
+    0x890, 0x890, 0x910, 0x890, 0x890, 0x8a9, 0xa9e, 0xabf,
+    0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
+    0xb1e, 0xa90, 0xabf, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
+    0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0x890, 0x910,
+    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x890, 0x910,
+    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x89e, 0x89f,
+    0x99e, 0x99e, 0x99e, 0x99e, 0x890, 0x890, 0x8a9, 0x8a9,
+};
+
+static void test_reset_values(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    for (size_t i = 0; i < G_N_ELEMENTS(k230_iomux_reset_values); i++) {
+        g_assert_cmphex(qtest_readl(qts, K230_IOMUX_BASE +
+                                    i * sizeof(uint32_t)), ==,
+                        k230_iomux_reset_values[i]);
+    }
+
+    qtest_quit(qts);
+}
+
+static void test_rw(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, K230_IOMUX_IO0, 0x12345678);
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO0), ==, 0x12345678);
+
+    qtest_writel(qts, K230_IOMUX_IO1, 0xa5a5a5a5);
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO1), ==, 0xa5a5a5a5);
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO0), ==, 0x12345678);
+
+    qtest_quit(qts);
+}
+
+static void test_last_reg_rw(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, K230_IOMUX_LAST, 0x00000abc);
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_LAST), ==, 0x00000abc);
+
+    qtest_quit(qts);
+}
+
+static void test_reset_after_write(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, K230_IOMUX_IO0, 0x12345678);
+    qtest_writel(qts, K230_IOMUX_IO1, 0xa5a5a5a5);
+    qtest_writel(qts, K230_IOMUX_LAST, 0x00000abc);
+
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO0), ==, 0x12345678);
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO1), ==, 0xa5a5a5a5);
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_LAST), ==, 0x00000abc);
+
+    qtest_system_reset(qts);
+
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO0), ==,
+                    k230_iomux_reset_values[0]);
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO1), ==,
+                    k230_iomux_reset_values[1]);
+    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_LAST), ==, 0);
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/k230-iomux/reset", test_reset_values);
+    qtest_add_func("/k230-iomux/rw", test_rw);
+    qtest_add_func("/k230-iomux/last-reg-rw", test_last_reg_rw);
+    qtest_add_func("/k230-iomux/reset-after-write", test_reset_after_write);
+
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860e21..cb8001ecbf 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -297,7 +297,10 @@ qtests_riscv64 = ['riscv-csr-test'] + \
   (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
    config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
    ['iommu-riscv-test'] : []) + \
-  (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
+  (config_all_devices.has_key('CONFIG_K230') ? [
+    'k230-wdt-test',
+    'k230-iomux-test',
+  ] : [])
 
 qtests_hexagon = ['boot-serial-test']
 
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH RFC 1/1] hw/riscv/k230: add IOMUX register block model
  2026-07-10  4:18 [PATCH RFC 1/1] hw/riscv/k230: add IOMUX register block model Kangjie Huang
@ 2026-07-13  1:33 ` Alistair Francis
  2026-07-13  9:19   ` Kangjie Huang
  2026-07-13  5:48 ` Chao Liu
  1 sibling, 1 reply; 4+ messages in thread
From: Alistair Francis @ 2026-07-13  1:33 UTC (permalink / raw)
  To: qemu-devel@nongnu.org, flamboyant.h.01@gmail.com
  Cc: chao.liu@processmission.com, daniel.barboza@oss.qualcomm.com,
	lvivier@redhat.com, qemu-riscv@nongnu.org,
	zhiwei_liu@linux.alibaba.com, palmer@dabbelt.com,
	pbonzini@redhat.com, liwei1518@gmail.com,
	pierrick.bouvier@oss.qualcomm.com, farosas@suse.de

On Fri, 2026-07-10 at 12:18 +0800, Kangjie Huang wrote:
> Add a sysbus model for the K230 IOMUX MMIO range at
> 0x91105000-0x911057ff. The model exposes the Function IO registers
> with
> the reset defaults documented by the K230 Technical Reference Manual
> and
> keeps 32-bit guest writes visible to firmware and the operating
> system.
> 
> This is enough for SDK U-Boot's pinctrl-single driver, which programs
> the board pin configuration with 32-bit read-modify-write accesses.
> 
> Wire the device into the K230 SoC and add qtest coverage for reset
> values, read/write storage and system reset.
> 
> Signed-off-by: Kangjie Huang <flamboyant.h.01@gmail.com>
> ---
> RFC notes:
> - This is my first QEMU upstream submission; feedback on patch
> structure

Thanks for the patch

>   and modelling boundaries would be appreciated.
> - The model covers the IOMUX MMIO range 0x91105000-0x911057ff.
> - It is intended to satisfy SDK U-Boot pinctrl-single register
> accesses.
> - PMU IOMUX and physical pin routing are intentionally not modeled.
> - Tested with qtest and SDK U-Boot smoke test.
> 
>  docs/system/riscv/k230.rst    |   1 +
>  hw/misc/Kconfig               |   3 +
>  hw/misc/k230_iomux.c          | 138
> ++++++++++++++++++++++++++++++++++
>  hw/misc/meson.build           |   2 +
>  hw/misc/trace-events          |   4 +
>  hw/riscv/Kconfig              |   1 +
>  hw/riscv/k230.c               |  11 ++-
>  include/hw/misc/k230_iomux.h  |  34 +++++++++
>  include/hw/riscv/k230.h       |   2 +
>  tests/qtest/k230-iomux-test.c | 107 ++++++++++++++++++++++++++
>  tests/qtest/meson.build       |   5 +-
>  11 files changed, 304 insertions(+), 4 deletions(-)
>  create mode 100644 hw/misc/k230_iomux.c
>  create mode 100644 include/hw/misc/k230_iomux.h
>  create mode 100644 tests/qtest/k230-iomux-test.c

Generally you will want to split this up more. Usually one patch to add
the model and then a second patch to connect it and a third patch for
tests.

> 
> diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst
> index cea8202e55..b160c401fd 100644
> --- a/docs/system/riscv/k230.rst
> +++ b/docs/system/riscv/k230.rst
> @@ -20,6 +20,7 @@ The ``k230`` machine supports the following
> devices:
>  * Platform-Level Interrupt Controller (PLIC)
>  * 2 K230 Watchdog Timer
>  * 5 UART
> +* K230 IOMUX register block
>  
>  Boot options
>  ------------
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 1543ee6653..573d24a9df 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -257,4 +257,7 @@ config XLNX_VERSAL_TRNG
>  config XLNX_ZYNQ_DDRC
>      bool
>  
> +config K230_IOMUX
> +    bool
> +
>  source macio/Kconfig
> diff --git a/hw/misc/k230_iomux.c b/hw/misc/k230_iomux.c
> new file mode 100644
> index 0000000000..5f5e2aac51
> --- /dev/null
> +++ b/hw/misc/k230_iomux.c
> @@ -0,0 +1,138 @@
> +/*
> + * K230 IOMUX register block compatible with Kendryte K230 SDK
> + *
> + * Copyright (c) 2026 Kangjie Huang <flamboyant.h.01@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Provides the low IOMUX register block at 0x91105000 with the
> reset
> + * defaults documented by the K230 Technical Reference Manual. The
> model keeps
> + * guest 32-bit register writes visible to firmware and the
> operating system,
> + * but does not model physical pin routing, pad electrical effects,
> or the
> + * separate PMU IOMUX block.
> + *
> + * 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 "hw/misc/k230_iomux.h"
> +#include "migration/vmstate.h"
> +#include "qemu/module.h"
> +#include "qemu/log.h"
> +#include "trace.h"
> +
> +/* TRM V0.3.1 section 12.9.2.1 Function IO register list. */
> +static const uint32_t k230_iomux_reset_values[] = {
> +    0x944, 0x944, 0x929, 0x908, 0x888, 0x908, 0x948, 0x890,
> +    0x890, 0x890, 0x910, 0x890, 0x890, 0x8a9, 0xa9e, 0xabf,
> +    0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
> +    0xb1e, 0xa90, 0xabf, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
> +    0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0x890, 0x910,
> +    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x890, 0x910,
> +    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x89e, 0x89f,
> +    0x99e, 0x99e, 0x99e, 0x99e, 0x890, 0x890, 0x8a9, 0x8a9,
> +};
> +
> +static void k230_iomux_reset(DeviceState *dev)
> +{
> +    K230IomuxState *s = K230_IOMUX(dev);
> +
> +    memset(s->regs, 0, sizeof(s->regs));
> +    memcpy(s->regs, k230_iomux_reset_values,
> +           sizeof(k230_iomux_reset_values));
> +}
> +
> +static uint64_t k230_iomux_read(void *opaque, hwaddr offset,
> unsigned size)
> +{
> +    K230IomuxState *s = K230_IOMUX(opaque);
> +    uint32_t value;
> +
> +    if (offset + size > K230_IOMUX_MMIO_SIZE || (offset & 3)) {

You don't need to check the offset or offset + size here, that is
enforced by your min_access_size/max_access_size

> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: invalid access offset 0x%" HWADDR_PRIx
> +                      " size %u\n", __func__, offset, size);
> +        return 0;
> +    }
> +
> +    value = s->regs[offset >> 2];
> +    trace_k230_iomux_read(offset, value);
> +    return value;
> +}
> +
> +static void k230_iomux_write(void *opaque, hwaddr offset,
> +                             uint64_t value, unsigned size)
> +{
> +    K230IomuxState *s = K230_IOMUX(opaque);
> +
> +    if (offset + size > K230_IOMUX_MMIO_SIZE || (offset & 3)) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: invalid access offset 0x%" HWADDR_PRIx
> +                      " size %u\n", __func__, offset, size);
> +        return;
> +    }
> +
> +    trace_k230_iomux_write(offset, value);
> +    s->regs[offset >> 2] = value;

Do you only ever expect to just access an array? So there is no other
functionality required here?

Alistair

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH RFC 1/1] hw/riscv/k230: add IOMUX register block model
  2026-07-10  4:18 [PATCH RFC 1/1] hw/riscv/k230: add IOMUX register block model Kangjie Huang
  2026-07-13  1:33 ` Alistair Francis
@ 2026-07-13  5:48 ` Chao Liu
  1 sibling, 0 replies; 4+ messages in thread
From: Chao Liu @ 2026-07-13  5:48 UTC (permalink / raw)
  To: Kangjie Huang
  Cc: qemu-devel, Pierrick Bouvier, Paolo Bonzini, Palmer Dabbelt,
	Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
	Fabiano Rosas, Laurent Vivier, open list:K230 Machines

On Fri, Jul 10, 2026 at 12:18:44PM +0800, Kangjie Huang wrote:
> Add a sysbus model for the K230 IOMUX MMIO range at
> 0x91105000-0x911057ff. The model exposes the Function IO registers with
> the reset defaults documented by the K230 Technical Reference Manual and
> keeps 32-bit guest writes visible to firmware and the operating system.
> 
> This is enough for SDK U-Boot's pinctrl-single driver, which programs
> the board pin configuration with 32-bit read-modify-write accesses.
> 
> Wire the device into the K230 SoC and add qtest coverage for reset
> values, read/write storage and system reset.
> 
> Signed-off-by: Kangjie Huang <flamboyant.h.01@gmail.com>
> ---
> RFC notes:
> - This is my first QEMU upstream submission; feedback on patch structure
>   and modelling boundaries would be appreciated.
> - The model covers the IOMUX MMIO range 0x91105000-0x911057ff.
> - It is intended to satisfy SDK U-Boot pinctrl-single register accesses.
> - PMU IOMUX and physical pin routing are intentionally not modeled.
> - Tested with qtest and SDK U-Boot smoke test.
> 
>  docs/system/riscv/k230.rst    |   1 +
>  hw/misc/Kconfig               |   3 +
>  hw/misc/k230_iomux.c          | 138 ++++++++++++++++++++++++++++++++++
>  hw/misc/meson.build           |   2 +
>  hw/misc/trace-events          |   4 +
>  hw/riscv/Kconfig              |   1 +
>  hw/riscv/k230.c               |  11 ++-
>  include/hw/misc/k230_iomux.h  |  34 +++++++++
>  include/hw/riscv/k230.h       |   2 +
>  tests/qtest/k230-iomux-test.c | 107 ++++++++++++++++++++++++++
>  tests/qtest/meson.build       |   5 +-
>  11 files changed, 304 insertions(+), 4 deletions(-)
>  create mode 100644 hw/misc/k230_iomux.c
>  create mode 100644 include/hw/misc/k230_iomux.h
>  create mode 100644 tests/qtest/k230-iomux-test.c
> 
> diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst
> index cea8202e55..b160c401fd 100644
> --- a/docs/system/riscv/k230.rst
> +++ b/docs/system/riscv/k230.rst
> @@ -20,6 +20,7 @@ The ``k230`` machine supports the following devices:
>  * Platform-Level Interrupt Controller (PLIC)
>  * 2 K230 Watchdog Timer
>  * 5 UART
> +* K230 IOMUX register block
>  
>  Boot options
>  ------------
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 1543ee6653..573d24a9df 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -257,4 +257,7 @@ config XLNX_VERSAL_TRNG
>  config XLNX_ZYNQ_DDRC
>      bool
>  
> +config K230_IOMUX
> +    bool
> +
>  source macio/Kconfig
> diff --git a/hw/misc/k230_iomux.c b/hw/misc/k230_iomux.c
> new file mode 100644
> index 0000000000..5f5e2aac51
> --- /dev/null
> +++ b/hw/misc/k230_iomux.c
> @@ -0,0 +1,138 @@
> +/*
> + * K230 IOMUX register block compatible with Kendryte K230 SDK
> + *
> + * Copyright (c) 2026 Kangjie Huang <flamboyant.h.01@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Provides the low IOMUX register block at 0x91105000 with the reset
> + * defaults documented by the K230 Technical Reference Manual. The model keeps
> + * guest 32-bit register writes visible to firmware and the operating system,
> + * but does not model physical pin routing, pad electrical effects, or the
> + * separate PMU IOMUX block.
> + *
> + * 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 "hw/misc/k230_iomux.h"
> +#include "migration/vmstate.h"
> +#include "qemu/module.h"
> +#include "qemu/log.h"
> +#include "trace.h"
> +
> +/* TRM V0.3.1 section 12.9.2.1 Function IO register list. */
> +static const uint32_t k230_iomux_reset_values[] = {
> +    0x944, 0x944, 0x929, 0x908, 0x888, 0x908, 0x948, 0x890,
> +    0x890, 0x890, 0x910, 0x890, 0x890, 0x8a9, 0xa9e, 0xabf,
> +    0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
> +    0xb1e, 0xa90, 0xabf, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
> +    0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0x890, 0x910,
> +    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x890, 0x910,
> +    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x89e, 0x89f,
> +    0x99e, 0x99e, 0x99e, 0x99e, 0x890, 0x890, 0x8a9, 0x8a9,
> +};
> +
> +static void k230_iomux_reset(DeviceState *dev)
> +{
> +    K230IomuxState *s = K230_IOMUX(dev);
> +
> +    memset(s->regs, 0, sizeof(s->regs));
> +    memcpy(s->regs, k230_iomux_reset_values,
> +           sizeof(k230_iomux_reset_values));
> +}
> +
> +static uint64_t k230_iomux_read(void *opaque, hwaddr offset, unsigned size)
> +{
> +    K230IomuxState *s = K230_IOMUX(opaque);
> +    uint32_t value;
> +
> +    if (offset + size > K230_IOMUX_MMIO_SIZE || (offset & 3)) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: invalid access offset 0x%" HWADDR_PRIx
> +                      " size %u\n", __func__, offset, size);
> +        return 0;
> +    }
> +
> +    value = s->regs[offset >> 2];
> +    trace_k230_iomux_read(offset, value);
> +    return value;
> +}
> +
> +static void k230_iomux_write(void *opaque, hwaddr offset,
> +                             uint64_t value, unsigned size)
> +{
> +    K230IomuxState *s = K230_IOMUX(opaque);
> +
> +    if (offset + size > K230_IOMUX_MMIO_SIZE || (offset & 3)) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: invalid access offset 0x%" HWADDR_PRIx
> +                      " size %u\n", __func__, offset, size);
> +        return;
> +    }
> +
> +    trace_k230_iomux_write(offset, value);
> +    s->regs[offset >> 2] = value;
The IOMUX register only allows access to bit 31 and bits 0 through 13.

Please see the link provided below. Additionally, you should pay attention to the read/write
permissions for every bit within each register.

https://www.kendryte.com/k230_linux/en/main/app_develop_guide/driver/iomux.html#registers
> +}
> +
> +static const MemoryRegionOps k230_iomux_ops = {
> +    .read = k230_iomux_read,
> +    .write = k230_iomux_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +        .unaligned = false,
> +    },
> +    .impl = {
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +        .unaligned = false,
> +    },
> +};
> +
> +static void k230_iomux_init(Object *obj)
> +{
> +    K230IomuxState *s = K230_IOMUX(obj);
> +
> +    memory_region_init_io(&s->mmio, obj, &k230_iomux_ops, s,
> +                          TYPE_K230_IOMUX, K230_IOMUX_MMIO_SIZE);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
> +}
> +
> +static const VMStateDescription vmstate_k230_iomux = {
> +    .name = "k230.iomux",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_UINT32_ARRAY(regs, K230IomuxState, K230_IOMUX_NUM_REGS),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void k230_iomux_class_init(ObjectClass *klass, const void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->desc = "Kendryte K230 IOMUX";
> +    dc->vmsd = &vmstate_k230_iomux;
> +    device_class_set_legacy_reset(dc, k230_iomux_reset);
> +}
> +
> +static const TypeInfo k230_iomux_info = {
> +    .name          = TYPE_K230_IOMUX,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(K230IomuxState),
> +    .instance_init = k230_iomux_init,
> +    .class_init    = k230_iomux_class_init,
> +};
> +
> +static void k230_iomux_register_types(void)
> +{
> +    type_register_static(&k230_iomux_info);
> +}
> +
> +type_init(k230_iomux_register_types)
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index 23265f6035..31e5145a35 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -168,3 +168,5 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
>  
>  # HPPA devices
>  system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
> +
> +system_ss.add(when: 'CONFIG_K230_IOMUX', if_true: files('k230_iomux.c'))
> diff --git a/hw/misc/trace-events b/hw/misc/trace-events
> index c9a868b3ef..388967c9e1 100644
> --- a/hw/misc/trace-events
> +++ b/hw/misc/trace-events
> @@ -405,6 +405,10 @@ djmemc_write(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRI
>  iosb_read(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u"
>  iosb_write(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u"
>  
> +# k230_iomux.c
> +k230_iomux_read(uint64_t offset, uint32_t value) "offset=0x%" PRIx64 " value=0x%" PRIx32
> +k230_iomux_write(uint64_t offset, uint64_t value) "offset=0x%" PRIx64 " value=0x%" PRIx64
> +
>  # aspeed_sli.c
>  aspeed_sli_write(uint64_t offset, unsigned int size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32
>  aspeed_sli_read(uint64_t offset, unsigned int size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index de37c08cae..4c9fc3e7ad 100644
> --- a/hw/riscv/Kconfig
> +++ b/hw/riscv/Kconfig
> @@ -162,3 +162,4 @@ config K230
>      select SERIAL_MM
>      select UNIMP
>      select K230_WDT
> +    select K230_IOMUX
> diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
> index 656f28190c..430c1646b5 100644
> --- a/hw/riscv/k230.c
> +++ b/hw/riscv/k230.c
> @@ -110,6 +110,7 @@ static void k230_soc_init(Object *obj)
>      object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY);
>      object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT);
>      object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT);
> +    object_initialize_child(obj, "k230-iomux", &s->iomux, TYPE_K230_IOMUX);
>  
>      qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
>      qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
> @@ -198,6 +199,11 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
>          }
>      }
>  
> +    /* IOMUX */
> +    if (!sysbus_realize(SYS_BUS_DEVICE(&s->iomux), 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));
> @@ -206,6 +212,8 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
>      sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[1]), 0,
>                         qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT1_IRQ));
>  
> +    sysbus_mmio_map(SYS_BUS_DEVICE(&s->iomux), 0, memmap[K230_DEV_IOMUX].base);
> +
>      /* unimplemented devices */
>      create_unimplemented_device("kpu.l2-cache",
>                                  memmap[K230_DEV_KPU_L2_CACHE].base,
> @@ -280,9 +288,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
>      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);
>  
> diff --git a/include/hw/misc/k230_iomux.h b/include/hw/misc/k230_iomux.h
> new file mode 100644
> index 0000000000..15e4bb339d
> --- /dev/null
> +++ b/include/hw/misc/k230_iomux.h
> @@ -0,0 +1,34 @@
> +/*
> + * Kendryte K230 IOMUX register block
> + *
> + * This models the low IOMUX register block. Physical pin routing, pad
> + * electrical effects, and the separate PMU IOMUX block are out of scope.
> + *
> + * 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) 2026 Kangjie Huang <flamboyant.h.01@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_K230_IOMUX_H
> +#define HW_MISC_K230_IOMUX_H
> +
> +#include "hw/core/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_K230_IOMUX "riscv.k230.iomux"
> +OBJECT_DECLARE_SIMPLE_TYPE(K230IomuxState, K230_IOMUX)
> +
> +#define K230_IOMUX_MMIO_SIZE 0x800
> +#define K230_IOMUX_NUM_REGS (K230_IOMUX_MMIO_SIZE / sizeof(uint32_t))
> +
> +struct K230IomuxState {
> +    SysBusDevice parent_obj;
> +
> +    MemoryRegion mmio;
> +    uint32_t regs[K230_IOMUX_NUM_REGS];
> +};
> +
> +#endif /* HW_MISC_K230_IOMUX_H */
> diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
> index 592e1c26bf..223e76b420 100644
> --- a/include/hw/riscv/k230.h
> +++ b/include/hw/riscv/k230.h
> @@ -18,6 +18,7 @@
>  #include "hw/core/boards.h"
>  #include "hw/riscv/riscv_hart.h"
>  #include "hw/watchdog/k230_wdt.h"
> +#include "hw/misc/k230_iomux.h"
>  
>  #define C908_CPU_HARTID   (0)
>  
> @@ -33,6 +34,7 @@ typedef struct K230SoCState {
>      RISCVHartArrayState c908_cpu; /* Small core */
>  
>      K230WdtState wdt[2];
> +    K230IomuxState iomux;
>      MemoryRegion sram;
>      MemoryRegion bootrom;
>  
> diff --git a/tests/qtest/k230-iomux-test.c b/tests/qtest/k230-iomux-test.c
> new file mode 100644
> index 0000000000..ca2d557955
> --- /dev/null
> +++ b/tests/qtest/k230-iomux-test.c
> @@ -0,0 +1,107 @@
> +/*
> + * QTest testcase for K230 IOMUX
> + *
> + * Copyright (c) 2026 Kangjie Huang <flamboyant.h.01@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Provides test coverage for the low IOMUX register block 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 "libqtest.h"
> +#include "hw/misc/k230_iomux.h"
> +
> +#define K230_IOMUX_BASE 0x91105000
> +#define K230_IOMUX_IO0  (K230_IOMUX_BASE + 0x00)
> +#define K230_IOMUX_IO1  (K230_IOMUX_BASE + 0x04)
> +#define K230_IOMUX_LAST (K230_IOMUX_BASE + K230_IOMUX_MMIO_SIZE - 4)
> +
> +static const uint32_t k230_iomux_reset_values[] = {
> +    0x944, 0x944, 0x929, 0x908, 0x888, 0x908, 0x948, 0x890,
> +    0x890, 0x890, 0x910, 0x890, 0x890, 0x8a9, 0xa9e, 0xabf,
> +    0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
> +    0xb1e, 0xa90, 0xabf, 0xb9e, 0xb9e, 0xb9e, 0xb9e, 0xb9e,
> +    0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0xbd0, 0x890, 0x910,
> +    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x890, 0x910,
> +    0x890, 0x910, 0x890, 0x910, 0x890, 0x910, 0x89e, 0x89f,
> +    0x99e, 0x99e, 0x99e, 0x99e, 0x890, 0x890, 0x8a9, 0x8a9,
> +};
> +
> +static void test_reset_values(void)
> +{
> +    QTestState *qts = qtest_init("-machine k230");
> +
> +    for (size_t i = 0; i < G_N_ELEMENTS(k230_iomux_reset_values); i++) {
> +        g_assert_cmphex(qtest_readl(qts, K230_IOMUX_BASE +
> +                                    i * sizeof(uint32_t)), ==,
> +                        k230_iomux_reset_values[i]);
> +    }
> +
> +    qtest_quit(qts);
> +}
> +
> +static void test_rw(void)
> +{
> +    QTestState *qts = qtest_init("-machine k230");
> +
> +    qtest_writel(qts, K230_IOMUX_IO0, 0x12345678);
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO0), ==, 0x12345678);
> +
> +    qtest_writel(qts, K230_IOMUX_IO1, 0xa5a5a5a5);
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO1), ==, 0xa5a5a5a5);
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO0), ==, 0x12345678);
> +
> +    qtest_quit(qts);
> +}
> +
> +static void test_last_reg_rw(void)
> +{
> +    QTestState *qts = qtest_init("-machine k230");
> +
> +    qtest_writel(qts, K230_IOMUX_LAST, 0x00000abc);
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_LAST), ==, 0x00000abc);
> +
> +    qtest_quit(qts);
> +}
> +
> +static void test_reset_after_write(void)
> +{
> +    QTestState *qts = qtest_init("-machine k230");
> +
> +    qtest_writel(qts, K230_IOMUX_IO0, 0x12345678);
> +    qtest_writel(qts, K230_IOMUX_IO1, 0xa5a5a5a5);
> +    qtest_writel(qts, K230_IOMUX_LAST, 0x00000abc);
> +
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO0), ==, 0x12345678);
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO1), ==, 0xa5a5a5a5);
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_LAST), ==, 0x00000abc);
> +
> +    qtest_system_reset(qts);
> +
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO0), ==,
> +                    k230_iomux_reset_values[0]);
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_IO1), ==,
> +                    k230_iomux_reset_values[1]);
> +    g_assert_cmphex(qtest_readl(qts, K230_IOMUX_LAST), ==, 0);
> +
> +    qtest_quit(qts);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    g_test_init(&argc, &argv, NULL);
> +
> +    qtest_add_func("/k230-iomux/reset", test_reset_values);
> +    qtest_add_func("/k230-iomux/rw", test_rw);
> +    qtest_add_func("/k230-iomux/last-reg-rw", test_last_reg_rw);
> +    qtest_add_func("/k230-iomux/reset-after-write", test_reset_after_write);
> +
> +    return g_test_run();
> +}
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 56ff860e21..cb8001ecbf 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -297,7 +297,10 @@ qtests_riscv64 = ['riscv-csr-test'] + \
>    (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
>     config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
>     ['iommu-riscv-test'] : []) + \
> -  (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
> +  (config_all_devices.has_key('CONFIG_K230') ? [
> +    'k230-wdt-test',
> +    'k230-iomux-test',
> +  ] : [])
>  
>  qtests_hexagon = ['boot-serial-test']
>  
> -- 
> 2.43.0
> 


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH RFC 1/1] hw/riscv/k230: add IOMUX register block model
  2026-07-13  1:33 ` Alistair Francis
@ 2026-07-13  9:19   ` Kangjie Huang
  0 siblings, 0 replies; 4+ messages in thread
From: Kangjie Huang @ 2026-07-13  9:19 UTC (permalink / raw)
  To: Alistair Francis
  Cc: Chao Liu, qemu-devel, qemu-riscv, Pierrick Bouvier, Paolo Bonzini,
	Palmer Dabbelt, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
	Fabiano Rosas, Laurent Vivier

Hi Alistair and Chao,

Thank you both very much for the reviews and references.

I will split v2 into three patches: one adding the IOMUX device model,
one wiring it into the K230 SoC, and one adding the qtest coverage.

I will also remove the explicit offset, size, and alignment checks from
the read and write callbacks, since the MemoryRegion size and
MemoryRegionOps access constraints already enforce the valid MMIO range
and aligned 32-bit accesses.

For the currently modeled and tested use cases, the device only needs
register-backed storage. The SDK U-Boot pinctrl-single driver and the
SDK Linux IOMUX-related code paths I checked use 32-bit
read-modify-write accesses and require the configuration values to be
retained.

On real hardware, writing these registers changes the pin function and
electrical configuration. However, the current K230 machine does not
model physical pin routing or electrical characteristics, and I have
not found any additional register side effects required by the U-Boot
or Linux code paths I tested.

I will update v2 to follow the documented bit-level access permissions:
bit 31 (DI) is read-only, bits 30 through 14 are read-only reserved bits,
and bits 13 through 0 are read-write configuration fields. Guest writes
will use a 0x00003fff writable mask, so they cannot modify bit 31 or the
reserved bits. I will also add qtests covering writable fields,
read-only fields, reserved bits, and normal read-modify-write accesses.

The SoC memory map reserves a 0x800-byte window for the Main IOMUX, but
the documentation defines only 64 pin registers at offsets 0x00 through
0xfc. I will model only these documented registers in v2 and leave
offsets 0x100 through 0x7ff reserved/unimplemented.

I will include these changes in v2.

Thanks,
Kangjie




^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-07-13  9:20 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10  4:18 [PATCH RFC 1/1] hw/riscv/k230: add IOMUX register block model Kangjie Huang
2026-07-13  1:33 ` Alistair Francis
2026-07-13  9:19   ` Kangjie Huang
2026-07-13  5:48 ` Chao Liu

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.