[PATCH 5/5] hw/riscv: wire K230 peripherals into the machine.

Leo Cheng <[email protected]>
Newsgroups org.nongnu.qemu-devel,org.nongnu.qemu-riscv
Message-ID <[email protected]>
Instantiate the GPIO, RTC, IOMUX, mailbox and SD/eMMC models on the k230 machine, add two SiFive PWM instances, and register the qtests. Also add a satellite-safety fault-injection qtest (watchdog timeout, protected-register tamper, GPIO interrupt storm/loss) exercising the modelled devices, update the machine documentation and MAINTAINERS.

Signed-off-by: Leo Cheng <[email protected]>
---
 MAINTAINERS                    |  12 +++-
 docs/system/riscv/k230.rst     |   6 ++
 hw/riscv/Kconfig               |   7 ++
 hw/riscv/k230.c                |  89 +++++++++++++++++++----
 include/hw/riscv/k230.h        |  18 +++++
 tests/qtest/k230-pwm-test.c    |  43 +++++++++++
 tests/qtest/k230-safety-test.c | 127 +++++++++++++++++++++++++++++++++
 tests/qtest/meson.build        |   2 +-
 8 files changed, 290 insertions(+), 14 deletions(-)
 create mode 100644 tests/qtest/k230-pwm-test.c
 create mode 100644 tests/qtest/k230-safety-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6171cc7..987a58f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1825,10 +1825,20 @@ L: [email protected]
 S: Maintained
 F: docs/system/riscv/k230.rst
 F: hw/riscv/k230.c
+F: hw/gpio/k230_gpio.c
+F: hw/rtc/k230_rtc.c
+F: hw/misc/k230_iomux.c
+F: hw/misc/k230_mailbox.c
+F: hw/sd/k230_dwmmc.c
 F: hw/watchdog/k230_wdt.c
 F: include/hw/riscv/k230.h
+F: include/hw/gpio/k230_gpio.h
+F: include/hw/rtc/k230_rtc.h
+F: include/hw/misc/k230_iomux.h
+F: include/hw/misc/k230_mailbox.h
+F: include/hw/sd/k230_dwmmc.h
 F: include/hw/watchdog/k230_wdt.h
-F: tests/qtest/k230-wdt-test.c
+F: tests/qtest/k230-*-test.c
 
 RX Machines
 -----------
diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst
index cea8202..7adafaf 100644
--- a/docs/system/riscv/k230.rst
+++ b/docs/system/riscv/k230.rst
@@ -20,6 +20,12 @@ The ``k230`` machine supports the following devices:
 * Platform-Level Interrupt Controller (PLIC)
 * 2 K230 Watchdog Timer
 * 5 UART
+* 2 GPIO controllers (DesignWare APB GPIO, one PLIC line per pin)
+* 2 PWM controllers (SiFive PWM)
+* RTC (date/time, alarm and periodic tick interrupts)
+* IOMUX pin-configuration controller
+* Mailbox / IPCM block (inter-core doorbell interrupts + hardware spinlock)
+* SD/eMMC host (DesignWare dw_mmc, register model)
 
 Boot options
 ------------
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index de37c08..82c9d7a 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -106,6 +106,7 @@ config SIFIVE_U
     select SIFIVE_U_OTP
     select SIFIVE_U_PRCI
     select SIFIVE_PWM
+    select K230_RTC
     select SSI_M25P80
     select SSI_SD
     select UNIMP
@@ -162,3 +163,9 @@ config K230
     select SERIAL_MM
     select UNIMP
     select K230_WDT
+    select K230_GPIO
+    select SIFIVE_PWM
+    select K230_RTC
+    select K230_IOMUX
+    select K230_MAILBOX
+    select K230_DWMMC
diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
index 656f281..e81b434 100644
--- a/hw/riscv/k230.c
+++ b/hw/riscv/k230.c
@@ -110,6 +110,16 @@ 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-gpio0", &s->gpio[0], TYPE_K230_GPIO);
+    object_initialize_child(obj, "k230-gpio1", &s->gpio[1], TYPE_K230_GPIO);
+    object_initialize_child(obj, "k230-pwm0", &s->pwm[0], TYPE_SIFIVE_PWM);
+    object_initialize_child(obj, "k230-pwm1", &s->pwm[1], TYPE_SIFIVE_PWM);
+    object_initialize_child(obj, "k230-rtc", &s->rtc, TYPE_K230_RTC);
+    object_initialize_child(obj, "k230-iomux", &s->iomux, TYPE_K230_IOMUX);
+    object_initialize_child(obj, "k230-mailbox", &s->mailbox,
+                            TYPE_K230_MAILBOX);
+    object_initialize_child(obj, "k230-sd0", &s->sd[0], TYPE_K230_DWMMC);
+    object_initialize_child(obj, "k230-sd1", &s->sd[1], TYPE_K230_DWMMC);
 
     qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
     qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
@@ -206,6 +216,73 @@ 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));
 
+    /* GPIO0/GPIO1: DW_apb_gpio, one PLIC line per pin */
+    for (int i = 0; i < 2; i++) {
+        SysBusDevice *sbd = SYS_BUS_DEVICE(&s->gpio[i]);
+        hwaddr base = i == 0 ? memmap[K230_DEV_GPIO0].base
+                          : memmap[K230_DEV_GPIO1].base;
+        int irq_base = i == 0 ? K230_GPIO0_IRQ : K230_GPIO1_IRQ;
+
+        if (!sysbus_realize(sbd, errp)) {
+            return;
+        }
+        sysbus_mmio_map(sbd, 0, base);
+        for (int pin = 0; pin < K230_GPIO_PINS; pin++) {
+            sysbus_connect_irq(sbd, pin,
+                qdev_get_gpio_in(DEVICE(s->c908_plic), irq_base + pin));
+        }
+    }
+
+    /* PWM: two SiFive PWM instances at +0x00/+0x40; K230 leaves IRQs unwired */
+    for (int i = 0; i < 2; i++) {
+        if (!sysbus_realize(SYS_BUS_DEVICE(&s->pwm[i]), errp)) {
+            return;
+        }
+    }
+    memory_region_add_subregion(sys_mem, memmap[K230_DEV_PWM].base,
+        sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->pwm[0]), 0));
+    memory_region_add_subregion_overlap(sys_mem,
+        memmap[K230_DEV_PWM].base + 0x40,
+        sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->pwm[1]), 0), 1);
+
+    /* RTC */
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->rtc), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->rtc), 0, memmap[K230_DEV_RTC].base);
+    sysbus_connect_irq(SYS_BUS_DEVICE(&s->rtc), 0,
+                       qdev_get_gpio_in(DEVICE(s->c908_plic), K230_RTC_IRQ));
+
+    /* IOMUX (pinctrl) */
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->iomux), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->iomux), 0, memmap[K230_DEV_IOMUX].base);
+
+    /* Mailbox/IPCM block: doorbell interrupts + hardware spinlock at 0xA0 */
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->mailbox), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->mailbox), 0,
+                    memmap[K230_DEV_MAILBOX].base);
+    for (int i = 0; i < K230_MB_CHANNELS; i++) {
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->mailbox), i,
+            qdev_get_gpio_in(DEVICE(s->c908_plic), K230_MAILBOX_IRQ + i));
+    }
+
+    /* SD/eMMC (DesignWare dw_mmc) */
+    for (int i = 0; i < 2; i++) {
+        int devid = i == 0 ? K230_DEV_SD0 : K230_DEV_SD1;
+        int irq = i == 0 ? K230_SD0_IRQ : K230_SD1_IRQ;
+
+        if (!sysbus_realize(SYS_BUS_DEVICE(&s->sd[i]), errp)) {
+            return;
+        }
+        sysbus_mmio_map(SYS_BUS_DEVICE(&s->sd[i]), 0, memmap[devid].base);
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->sd[i]), 0,
+                           qdev_get_gpio_in(DEVICE(s->c908_plic), irq));
+    }
+
     /* unimplemented devices */
     create_unimplemented_device("kpu.l2-cache",
                                 memmap[K230_DEV_KPU_L2_CACHE].base,
@@ -262,9 +339,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
     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);
 
@@ -319,15 +393,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
     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);
 
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
index 592e1c2..e045f96 100644
--- a/include/hw/riscv/k230.h
+++ b/include/hw/riscv/k230.h
@@ -18,6 +18,12 @@
 #include "hw/core/boards.h"
 #include "hw/riscv/riscv_hart.h"
 #include "hw/watchdog/k230_wdt.h"
+#include "hw/gpio/k230_gpio.h"
+#include "hw/timer/sifive_pwm.h"
+#include "hw/rtc/k230_rtc.h"
+#include "hw/misc/k230_iomux.h"
+#include "hw/misc/k230_mailbox.h"
+#include "hw/sd/k230_dwmmc.h"
 
 #define C908_CPU_HARTID   (0)
 
@@ -33,6 +39,12 @@ typedef struct K230SoCState {
     RISCVHartArrayState c908_cpu; /* Small core */
 
     K230WdtState wdt[2];
+    K230GpioState gpio[2];
+    SiFivePwmState pwm[2];
+    K230RtcState rtc;
+    K230IomuxState iomux;
+    K230MailboxState mailbox;
+    K230DwmmcState sd[2];
     MemoryRegion sram;
     MemoryRegion bootrom;
 
@@ -127,8 +139,14 @@ enum {
     K230_UART2_IRQ  = 18,
     K230_UART3_IRQ  = 19,
     K230_UART4_IRQ  = 20,
+    K230_GPIO0_IRQ  = 32,   /* per-pin: 32..63 */
+    K230_GPIO1_IRQ  = 64,   /* per-pin: 64..95 */
+    K230_RTC_IRQ    = 175,  /* shared PMU/RTC */
     K230_WDT0_IRQ   = 107,
     K230_WDT1_IRQ   = 108,
+    K230_MAILBOX_IRQ = 109,  /* IPCM doorbell 109..112; ch0/ch1 use 109/110 */
+    K230_SD0_IRQ    = 142,
+    K230_SD1_IRQ    = 144,
 };
 
 #define K230_UART_COUNT 5
diff --git a/tests/qtest/k230-pwm-test.c b/tests/qtest/k230-pwm-test.c
new file mode 100644
index 0000000..a8b3dc3
--- /dev/null
+++ b/tests/qtest/k230-pwm-test.c
@@ -0,0 +1,43 @@
+/*
+ * QTest testcase for K230 PWM (two SiFive PWM instances)
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define PWM0_BASE 0x9140A000
+#define PWM1_BASE 0x9140A040
+
+#define PWM_PWMCMP0 0x20
+#define PWM_PWMCMP1 0x24
+
+/* Both instances are mapped and independently addressable */
+static void test_two_instances(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, PWM0_BASE + PWM_PWMCMP0, 0x1234);
+    qtest_writel(qts, PWM1_BASE + PWM_PWMCMP0, 0x5678);
+
+    g_assert_cmphex(qtest_readl(qts, PWM0_BASE + PWM_PWMCMP0), ==, 0x1234);
+    g_assert_cmphex(qtest_readl(qts, PWM1_BASE + PWM_PWMCMP0), ==, 0x5678);
+
+    /* Independent compare registers within an instance */
+    qtest_writel(qts, PWM0_BASE + PWM_PWMCMP1, 0x0abc);
+    g_assert_cmphex(qtest_readl(qts, PWM0_BASE + PWM_PWMCMP1), ==, 0x0abc);
+    g_assert_cmphex(qtest_readl(qts, PWM0_BASE + PWM_PWMCMP0), ==, 0x1234);
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/k230-pwm/two_instances", test_two_instances);
+
+    return g_test_run();
+}
diff --git a/tests/qtest/k230-safety-test.c b/tests/qtest/k230-safety-test.c
new file mode 100644
index 0000000..9d6cddf
--- /dev/null
+++ b/tests/qtest/k230-safety-test.c
@@ -0,0 +1,127 @@
+/*
+ * QTest testcase for K230 satellite-safety fault-injection scenarios
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Each scenario injects a fault through the modelled devices and checks a
+ * detect -> respond -> recover flow observable from the guest side (register
+ * status, PLIC pending, masked-vs-raw divergence).
+ */
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+#include "qemu/bitops.h"
+#include "libqtest.h"
+#include "hw/watchdog/k230_wdt.h"
+#include "hw/rtc/k230_rtc.h"
+#include "hw/gpio/k230_gpio.h"
+
+#define WDT0_BASE    0x91106000
+#define RTC_BASE     0x91000C00
+#define GPIO0_BASE   0x9140B000
+#define PLIC_BASE    0xF00000000ULL
+#define PLIC_PENDING (PLIC_BASE + 0x1000)
+#define WDT0_IRQ     107
+#define GPIO0_PIN0_IRQ 32
+
+static bool plic_pending(QTestState *qts, int irq)
+{
+    uint32_t word = qtest_readl(qts, PLIC_PENDING + (irq / 32) * 4);
+    return (word >> (irq % 32)) & 1;
+}
+
+/* Fault: watchdog timeout. Detect via STAT/PLIC, recover via EOI + restart. */
+static void test_wdt_timeout(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, WDT0_BASE + K230_WDT_CR,
+                 K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN);
+    qtest_writel(qts, WDT0_BASE + K230_WDT_TORR, 0x1);
+
+    qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 10);   /* let it expire */
+
+    /* detect */
+    g_assert_cmphex(qtest_readl(qts, WDT0_BASE + K230_WDT_STAT) &
+                    K230_WDT_STAT_INT, ==, K230_WDT_STAT_INT);
+    g_assert_true(plic_pending(qts, WDT0_IRQ));
+
+    /* recover: ack + kick */
+    qtest_writel(qts, WDT0_BASE + K230_WDT_EOI, 0x1);
+    g_assert_cmphex(qtest_readl(qts, WDT0_BASE + K230_WDT_STAT) &
+                    K230_WDT_STAT_INT, ==, 0);
+    qtest_writel(qts, WDT0_BASE + K230_WDT_CRR, K230_WDT_CRR_RESTART);
+
+    qtest_quit(qts);
+}
+
+/* Fault: tamper a protected register with the write lock closed. */
+static void test_rtc_tamper_rejected(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+    uint32_t golden = (12 << 16) | (30 << 8) | 45;
+
+    /* establish golden state under the write-enable gate */
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, K230_RTC_TIMER_W_EN);
+    qtest_writel(qts, RTC_BASE + K230_RTC_TIME, golden);
+    g_assert_cmphex(qtest_readl(qts, RTC_BASE + K230_RTC_TIME), ==, golden);
+
+    /* inject: close the gate, then attempt to tamper */
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, 0);
+    qtest_writel(qts, RTC_BASE + K230_RTC_TIME, 0xdeadbeef);
+
+    /* detect: read-back still equals golden -> tamper rejected */
+    g_assert_cmphex(qtest_readl(qts, RTC_BASE + K230_RTC_TIME), ==, golden);
+
+    qtest_quit(qts);
+}
+
+/* Fault: interrupt storm, then masked loss. Detect via raw-vs-masked. */
+static void test_gpio_storm_and_loss(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    /* pin0: output, rising-edge interrupt, unmasked */
+    qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DDR, 0x1);
+    qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTTYPE_LEVEL, 0x1);
+    qtest_writel(qts, GPIO0_BASE + K230_GPIO_INT_POLARITY, 0x1);
+    qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTEN, 0x1);
+
+    /* storm: repeated rising edges */
+    for (int i = 0; i < 16; i++) {
+        qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x0);
+        qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x1);
+    }
+    /* detect the storm: status latched + PLIC pending */
+    g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 1,
+                    ==, 1);
+    g_assert_true(plic_pending(qts, GPIO0_PIN0_IRQ));
+    qtest_writel(qts, GPIO0_BASE + K230_GPIO_PORTA_EOI, 0x1);   /* recover */
+
+    /* inject loss: mask pin0, generate an edge */
+    qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTMASK, 0x1);
+    qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x0);
+    qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x1);
+
+    /* detect loss: raw sees the edge, masked status does not */
+    g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_RAW_INTSTATUS) & 1,
+                    ==, 1);
+    g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 1,
+                    ==, 0);
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/k230-safety/wdt_timeout", test_wdt_timeout);
+    qtest_add_func("/k230-safety/rtc_tamper_rejected",
+                   test_rtc_tamper_rejected);
+    qtest_add_func("/k230-safety/gpio_storm_and_loss",
+                   test_gpio_storm_and_loss);
+
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860..be9e9ce 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -297,7 +297,7 @@ 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-gpio-test', 'k230-pwm-test', 'k230-rtc-test', 'k230-iomux-test', 'k230-mailbox-test', 'k230-safety-test', 'k230-dwmmc-test'] : [])
 
 qtests_hexagon = ['boot-serial-test']
 
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.