IOMUX is the K230 pin-configuration controller (64 pins, one 32-bit register 
per pin) with the field layout of the upstream Linux pinctrl-k230 driver. The 
mailbox/IPCM block at 0x91104000 is an inter-core interrupt-notify mailbox plus 
a hardware spinlock array: a write to a CPU2DSP/DSP2CPU SET register rings the 
doorbell IRQ, CLEAR acks, and the 0xA0 array is 128 test-and-set locks. Message 
payloads travel through shared memory, so no data registers are modelled. 
Register semantics from the Canaan k230_sdk. Covered by the k230-iomux and 
k230-mailbox qtests.

Signed-off-by: Leo Cheng <[email protected]>
---
 hw/misc/Kconfig                 |   6 +
 hw/misc/k230_iomux.c            | 101 ++++++++++++++++
 hw/misc/k230_mailbox.c          | 206 ++++++++++++++++++++++++++++++++
 hw/misc/meson.build             |   2 +
 hw/misc/trace-events            |  10 ++
 include/hw/misc/k230_iomux.h    |  44 +++++++
 include/hw/misc/k230_mailbox.h  |  61 ++++++++++
 tests/qtest/k230-iomux-test.c   |  62 ++++++++++
 tests/qtest/k230-mailbox-test.c |  89 ++++++++++++++
 9 files changed, 581 insertions(+)
 create mode 100644 hw/misc/k230_iomux.c
 create mode 100644 hw/misc/k230_mailbox.c
 create mode 100644 include/hw/misc/k230_iomux.h
 create mode 100644 include/hw/misc/k230_mailbox.h
 create mode 100644 tests/qtest/k230-iomux-test.c
 create mode 100644 tests/qtest/k230-mailbox-test.c

diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6..742ea2f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -186,6 +186,12 @@ config AUX
 config UNIMP
     bool
 
+config K230_IOMUX
+    bool
+
+config K230_MAILBOX
+    bool
+
 config LED
     bool
 
diff --git a/hw/misc/k230_iomux.c b/hw/misc/k230_iomux.c
new file mode 100644
index 0000000..87e7587
--- /dev/null
+++ b/hw/misc/k230_iomux.c
@@ -0,0 +1,101 @@
+/*
+ * K230 IOMUX (pin configuration) controller
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * K230 Technical Reference Manual V0.3.1 (2024-11-18); register layout matches
+ * the upstream Linux driver drivers/pinctrl/pinctrl-k230.c.
+ */
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/core/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/misc/k230_iomux.h"
+#include "trace.h"
+
+static uint64_t k230_iomux_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    K230IomuxState *s = K230_IOMUX(opaque);
+    uint32_t value = 0;
+
+    if (addr / 4 < K230_IOMUX_NPINS) {
+        value = s->pin[addr / 4];
+    }
+
+    trace_k230_iomux_read(addr, value);
+    return value;
+}
+
+static void k230_iomux_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned int size)
+{
+    K230IomuxState *s = K230_IOMUX(opaque);
+
+    trace_k230_iomux_write(addr, value);
+
+    if (addr / 4 < K230_IOMUX_NPINS) {
+        s->pin[addr / 4] = value & K230_PC_MASK;
+    }
+}
+
+static const MemoryRegionOps k230_iomux_ops = {
+    .read = k230_iomux_read,
+    .write = k230_iomux_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void k230_iomux_reset(DeviceState *dev)
+{
+    K230IomuxState *s = K230_IOMUX(dev);
+
+    memset(s->pin, 0, sizeof(s->pin));
+}
+
+static const VMStateDescription vmstate_k230_iomux = {
+    .name = "k230.iomux",
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(pin, K230IomuxState, K230_IOMUX_NPINS),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void k230_iomux_realize(DeviceState *dev, Error **errp)
+{
+    K230IomuxState *s = K230_IOMUX(dev);
+
+    memory_region_init_io(&s->mmio, OBJECT(dev), &k230_iomux_ops, s,
+                          TYPE_K230_IOMUX, K230_IOMUX_MMIO_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
+}
+
+static void k230_iomux_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = k230_iomux_realize;
+    device_class_set_legacy_reset(dc, k230_iomux_reset);
+    dc->vmsd = &vmstate_k230_iomux;
+    dc->desc = "K230 IOMUX";
+}
+
+static const TypeInfo k230_iomux_info = {
+    .name          = TYPE_K230_IOMUX,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(K230IomuxState),
+    .class_init    = k230_iomux_class_init,
+};
+
+static void k230_iomux_register_type(void)
+{
+    type_register_static(&k230_iomux_info);
+}
+
+type_init(k230_iomux_register_type)
diff --git a/hw/misc/k230_mailbox.c b/hw/misc/k230_mailbox.c
new file mode 100644
index 0000000..efcb843
--- /dev/null
+++ b/hw/misc/k230_mailbox.c
@@ -0,0 +1,206 @@
+/*
+ * K230 mailbox / IPCM block (inter-core interrupt notify + hardware spinlock)
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Register semantics from the Canaan k230_sdk: ipcm_platform.h (interrupt
+ * register offsets/bits), platform_riscv_rtsmart.c (SET rings the doorbell,
+ * CLEAR acks), drv_hardlock.c (test-and-set array at 0xA0). Message payloads
+ * are not carried by any register; they travel through shared memory.
+ */
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "hw/misc/k230_mailbox.h"
+#include "trace.h"
+
+static void k230_mailbox_update_irq(K230MailboxState *s, int ch)
+{
+    bool level = s->status[ch] && (s->en[ch] & K230_MB_INT_EN);
+
+    qemu_set_irq(s->irq[ch], level);
+}
+
+static int k230_mailbox_lock_num(hwaddr addr)
+{
+    if (addr < K230_MB_HARDLOCK_BASE) {
+        return -1;
+    }
+    addr -= K230_MB_HARDLOCK_BASE;
+    if (addr / 4 >= K230_MB_HARDLOCK_MAX) {
+        return -1;
+    }
+    return addr / 4;
+}
+
+static uint64_t k230_mailbox_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    K230MailboxState *s = K230_MAILBOX(opaque);
+    int num = k230_mailbox_lock_num(addr);
+    uint32_t value = 0;
+
+    if (num >= 0) {
+        value = s->lock[num];   /* 0 = free (now taken), 1 = already held */
+        s->lock[num] = 1;
+        trace_k230_mailbox_lock_read(num, value);
+        return value;
+    }
+
+    switch (addr) {
+    case K230_MB_CPU2DSP_EN:
+        value = s->en[0];
+        break;
+    case K230_MB_CPU2DSP_STATUS:
+        value = s->status[0];
+        break;
+    case K230_MB_CPU2DSP_ERR:
+        value = s->err[0];
+        break;
+    case K230_MB_DSP2CPU_EN:
+        value = s->en[1];
+        break;
+    case K230_MB_DSP2CPU_STATUS:
+        value = s->status[1];
+        break;
+    case K230_MB_DSP2CPU_ERR:
+        value = s->err[1];
+        break;
+    default:
+        break;
+    }
+
+    trace_k230_mailbox_read(addr, value);
+    return value;
+}
+
+static void k230_mailbox_write(void *opaque, hwaddr addr,
+                               uint64_t value, unsigned int size)
+{
+    K230MailboxState *s = K230_MAILBOX(opaque);
+    int num = k230_mailbox_lock_num(addr);
+
+    if (num >= 0) {
+        s->lock[num] = value & 1;
+        trace_k230_mailbox_lock_write(num, value);
+        return;
+    }
+
+    trace_k230_mailbox_write(addr, value);
+
+    switch (addr) {
+    case K230_MB_CPU2DSP_EN:
+        s->en[0] = value;
+        if (value & K230_MB_INT_SOFTRST) {
+            s->status[0] = 0;
+        }
+        k230_mailbox_update_irq(s, 0);
+        break;
+    case K230_MB_CPU2DSP_SET:
+        s->status[0] = 1;
+        k230_mailbox_update_irq(s, 0);
+        break;
+    case K230_MB_CPU2DSP_CLEAR:
+        s->status[0] = 0;
+        k230_mailbox_update_irq(s, 0);
+        break;
+    case K230_MB_CPU2DSP_ERR:
+        s->err[0] = value;
+        break;
+    case K230_MB_DSP2CPU_EN:
+        s->en[1] = value;
+        if (value & K230_MB_INT_SOFTRST) {
+            s->status[1] = 0;
+        }
+        k230_mailbox_update_irq(s, 1);
+        break;
+    case K230_MB_DSP2CPU_SET:
+        s->status[1] = 1;
+        k230_mailbox_update_irq(s, 1);
+        break;
+    case K230_MB_DSP2CPU_CLEAR:
+        s->status[1] = 0;
+        k230_mailbox_update_irq(s, 1);
+        break;
+    case K230_MB_DSP2CPU_ERR:
+        s->err[1] = value;
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps k230_mailbox_ops = {
+    .read = k230_mailbox_read,
+    .write = k230_mailbox_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void k230_mailbox_reset(DeviceState *dev)
+{
+    K230MailboxState *s = K230_MAILBOX(dev);
+
+    memset(s->en, 0, sizeof(s->en));
+    memset(s->status, 0, sizeof(s->status));
+    memset(s->err, 0, sizeof(s->err));
+    memset(s->lock, 0, sizeof(s->lock));
+    for (int i = 0; i < K230_MB_CHANNELS; i++) {
+        qemu_set_irq(s->irq[i], 0);
+    }
+}
+
+static const VMStateDescription vmstate_k230_mailbox = {
+    .name = "k230.mailbox",
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(en, K230MailboxState, K230_MB_CHANNELS),
+        VMSTATE_UINT32_ARRAY(status, K230MailboxState, K230_MB_CHANNELS),
+        VMSTATE_UINT32_ARRAY(err, K230MailboxState, K230_MB_CHANNELS),
+        VMSTATE_UINT8_ARRAY(lock, K230MailboxState, K230_MB_HARDLOCK_MAX),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void k230_mailbox_realize(DeviceState *dev, Error **errp)
+{
+    K230MailboxState *s = K230_MAILBOX(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    memory_region_init_io(&s->mmio, OBJECT(dev), &k230_mailbox_ops, s,
+                          TYPE_K230_MAILBOX, K230_MAILBOX_MMIO_SIZE);
+    sysbus_init_mmio(sbd, &s->mmio);
+    for (int i = 0; i < K230_MB_CHANNELS; i++) {
+        sysbus_init_irq(sbd, &s->irq[i]);
+    }
+}
+
+static void k230_mailbox_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = k230_mailbox_realize;
+    device_class_set_legacy_reset(dc, k230_mailbox_reset);
+    dc->vmsd = &vmstate_k230_mailbox;
+    dc->desc = "K230 mailbox/IPCM";
+}
+
+static const TypeInfo k230_mailbox_info = {
+    .name          = TYPE_K230_MAILBOX,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(K230MailboxState),
+    .class_init    = k230_mailbox_class_init,
+};
+
+static void k230_mailbox_register_type(void)
+{
+    type_register_static(&k230_mailbox_info);
+}
+
+type_init(k230_mailbox_register_type)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6..c0a678b 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -6,6 +6,8 @@ system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: 
files('pc-testdev.c'))
 system_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
 system_ss.add(when: 'CONFIG_IOMMU_TESTDEV', if_true: files('iommu-testdev.c'))
 system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
+system_ss.add(when: 'CONFIG_K230_IOMUX', if_true: files('k230_iomux.c'))
+system_ss.add(when: 'CONFIG_K230_MAILBOX', if_true: files('k230_mailbox.c'))
 system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
 system_ss.add(when: 'CONFIG_LED', if_true: files('led.c'))
 system_ss.add(when: 'CONFIG_PVPANIC_COMMON', if_true: files('pvpanic.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c9a868b..5bf28dd 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -442,3 +442,13 @@ iommu_testdev_dma_read(uint64_t gva, uint32_t len) 
"gva=0x%" PRIx64 " len=%u"
 iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x 
actual=0x%x"
 iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
 iommu_testdev_dma_armed(bool armed) "armed=%d"
+
+# k230_iomux.c
+k230_iomux_read(uint64_t addr, uint32_t val) "addr 0x%" PRIx64 " val 0x%08x"
+k230_iomux_write(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" 
PRIx64
+
+# k230_mailbox.c
+k230_mailbox_read(uint64_t addr, uint32_t val) "addr 0x%" PRIx64 " val 0x%08x"
+k230_mailbox_write(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" 
PRIx64
+k230_mailbox_lock_read(int num, uint32_t old) "lock %d old %u"
+k230_mailbox_lock_write(int num, uint64_t val) "lock %d val 0x%" PRIx64
diff --git a/include/hw/misc/k230_iomux.h b/include/hw/misc/k230_iomux.h
new file mode 100644
index 0000000..83c3acf
--- /dev/null
+++ b/include/hw/misc/k230_iomux.h
@@ -0,0 +1,44 @@
+/*
+ * K230 IOMUX (pin configuration) controller
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Register layout follows the upstream Linux pinctrl-k230 driver.
+ * (64 pins, one 32-bit config register per pin at pin*4).
+ */
+#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_NPINS     64
+
+/* Per-pin config bits (pinctrl-k230.c) */
+#define K230_PC_ST   (1u << 0)        /* schmitt trigger */
+#define K230_PC_DS   (0xfu << 1)      /* drive strength [4:1] */
+#define K230_PC_PD   (1u << 5)        /* pull-down */
+#define K230_PC_PU   (1u << 6)        /* pull-up */
+#define K230_PC_OE   (1u << 7)        /* output enable */
+#define K230_PC_IE   (1u << 8)        /* input enable */
+#define K230_PC_MSC  (1u << 9)        /* power/misc select */
+#define K230_PC_SL   (1u << 10)       /* slew rate */
+#define K230_PC_SEL  (0x7u << 11)     /* function/mux select [13:11] */
+#define K230_PC_MASK (K230_PC_ST | K230_PC_DS | K230_PC_PD | K230_PC_PU | \
+                      K230_PC_OE | K230_PC_IE | K230_PC_MSC | K230_PC_SL | \
+                      K230_PC_SEL)
+
+struct K230IomuxState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+    uint32_t pin[K230_IOMUX_NPINS];
+};
+
+#endif
diff --git a/include/hw/misc/k230_mailbox.h b/include/hw/misc/k230_mailbox.h
new file mode 100644
index 0000000..f939637
--- /dev/null
+++ b/include/hw/misc/k230_mailbox.h
@@ -0,0 +1,61 @@
+/*
+ * K230 mailbox / IPCM block (inter-core interrupt notify + hardware spinlock)
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The K230 mailbox at 0x91104000 is an interrupt-notification block, not a
+ * data FIFO: payloads travel through shared memory, the mailbox only rings a
+ * doorbell interrupt. Two sub-blocks share the 4KB page:
+ *   - IPCM interrupt registers 0x00-0x24 (CPU2DSP / DSP2CPU EN/SET/CLEAR/
+ *     STATUS/ERR), from Canaan k230_sdk ipcm_platform.h.
+ *   - hardware spinlock array at 0xA0 (128 test-and-set locks), from the
+ *     Canaan k230_sdk drv_hardlock.
+ */
+#ifndef HW_MISC_K230_MAILBOX_H
+#define HW_MISC_K230_MAILBOX_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_K230_MAILBOX "riscv.k230.mailbox"
+OBJECT_DECLARE_SIMPLE_TYPE(K230MailboxState, K230_MAILBOX)
+
+#define K230_MAILBOX_MMIO_SIZE 0x1000
+
+/* IPCM interrupt block (ipcm_platform.h) */
+#define K230_MB_CPU2DSP_EN     0x00
+#define K230_MB_CPU2DSP_SET    0x04
+#define K230_MB_CPU2DSP_CLEAR  0x08
+#define K230_MB_CPU2DSP_STATUS 0x0c
+#define K230_MB_CPU2DSP_ERR    0x10
+#define K230_MB_DSP2CPU_EN     0x14
+#define K230_MB_DSP2CPU_SET    0x18
+#define K230_MB_DSP2CPU_CLEAR  0x1c
+#define K230_MB_DSP2CPU_STATUS 0x20
+#define K230_MB_DSP2CPU_ERR    0x24
+
+#define K230_MB_INT_EN     (1u << 0)   /* INTR_EN_EABLE */
+#define K230_MB_INT_SOFTRST (1u << 1)  /* INTR_SOFT_RST_VALID */
+#define K230_MB_INT_RAW_EN (1u << 16)  /* INTR_RAW_EN_EABLE */
+
+/* hardware spinlock array */
+#define K230_MB_HARDLOCK_BASE 0xa0
+#define K230_MB_HARDLOCK_MAX  128
+
+#define K230_MB_CHANNELS 2   /* CPU2DSP, DSP2CPU */
+
+struct K230MailboxState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+    qemu_irq irq[K230_MB_CHANNELS];
+
+    uint32_t en[K230_MB_CHANNELS];
+    uint32_t status[K230_MB_CHANNELS];
+    uint32_t err[K230_MB_CHANNELS];
+    uint8_t lock[K230_MB_HARDLOCK_MAX];
+};
+
+#endif
diff --git a/tests/qtest/k230-iomux-test.c b/tests/qtest/k230-iomux-test.c
new file mode 100644
index 0000000..855d993
--- /dev/null
+++ b/tests/qtest/k230-iomux-test.c
@@ -0,0 +1,62 @@
+/*
+ * QTest testcase for K230 IOMUX
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "hw/misc/k230_iomux.h"
+
+#define IOMUX_BASE 0x91105000
+
+static void test_reset_values(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    g_assert_cmphex(qtest_readl(qts, IOMUX_BASE + 0), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, IOMUX_BASE + 63 * 4), ==, 0);
+
+    qtest_quit(qts);
+}
+
+/* Each pin config register is read/write; reserved bits above [13:0] drop */
+static void test_pin_rw(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+    uint32_t cfg = K230_PC_IE | K230_PC_PU | (3u << 11);  /* IE, PU, SEL=3 */
+
+    qtest_writel(qts, IOMUX_BASE + 5 * 4, cfg);
+    g_assert_cmphex(qtest_readl(qts, IOMUX_BASE + 5 * 4), ==, cfg);
+
+    qtest_writel(qts, IOMUX_BASE + 10 * 4, 0xFFFFFFFF);
+    g_assert_cmphex(qtest_readl(qts, IOMUX_BASE + 10 * 4), ==, K230_PC_MASK);
+
+    /* pin 5 unaffected by pin 10 */
+    g_assert_cmphex(qtest_readl(qts, IOMUX_BASE + 5 * 4), ==, cfg);
+
+    qtest_quit(qts);
+}
+
+/* Registers past the 64-pin window read back zero and ignore writes */
+static void test_out_of_range(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, IOMUX_BASE + 64 * 4, 0x1234);
+    g_assert_cmphex(qtest_readl(qts, IOMUX_BASE + 64 * 4), ==, 0);
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/k230-iomux/reset_values", test_reset_values);
+    qtest_add_func("/k230-iomux/pin_rw", test_pin_rw);
+    qtest_add_func("/k230-iomux/out_of_range", test_out_of_range);
+
+    return g_test_run();
+}
diff --git a/tests/qtest/k230-mailbox-test.c b/tests/qtest/k230-mailbox-test.c
new file mode 100644
index 0000000..c3869f0
--- /dev/null
+++ b/tests/qtest/k230-mailbox-test.c
@@ -0,0 +1,89 @@
+/*
+ * QTest testcase for K230 mailbox / IPCM block
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "hw/misc/k230_mailbox.h"
+
+#define MB_BASE      0x91104000
+#define PLIC_BASE    0xF00000000ULL
+#define PLIC_PENDING (PLIC_BASE + 0x1000)
+#define MB_IRQ0      109
+#define MB_IRQ1      110
+
+static bool plic_pending(QTestState *qts, int irq)
+{
+    uint32_t word = qtest_readl(qts, PLIC_PENDING + (irq / 32) * 4);
+    return (word >> (irq % 32)) & 1;
+}
+
+/* SET rings the doorbell only when the channel interrupt is enabled */
+static void test_doorbell_cpu2dsp(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    /* SET with interrupt disabled: no doorbell */
+    qtest_writel(qts, MB_BASE + K230_MB_CPU2DSP_SET, 0);
+    g_assert_false(plic_pending(qts, MB_IRQ0));
+    qtest_writel(qts, MB_BASE + K230_MB_CPU2DSP_CLEAR, 0);
+
+    /* enable, then SET rings the doorbell and latches STATUS */
+    qtest_writel(qts, MB_BASE + K230_MB_CPU2DSP_EN, K230_MB_INT_EN);
+    qtest_writel(qts, MB_BASE + K230_MB_CPU2DSP_SET, 0);
+    g_assert_true(plic_pending(qts, MB_IRQ0));
+    g_assert_cmpuint(qtest_readl(qts, MB_BASE + K230_MB_CPU2DSP_STATUS), ==, 
1);
+
+    /* CLEAR acks and drops STATUS */
+    qtest_writel(qts, MB_BASE + K230_MB_CPU2DSP_CLEAR, 0);
+    g_assert_cmpuint(qtest_readl(qts, MB_BASE + K230_MB_CPU2DSP_STATUS), ==, 
0);
+
+    qtest_quit(qts);
+}
+
+/* The reverse channel rings its own doorbell line */
+static void test_doorbell_dsp2cpu(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, MB_BASE + K230_MB_DSP2CPU_EN, K230_MB_INT_EN);
+    qtest_writel(qts, MB_BASE + K230_MB_DSP2CPU_SET, 0);
+    g_assert_true(plic_pending(qts, MB_IRQ1));
+    g_assert_false(plic_pending(qts, MB_IRQ0));   /* independent of ch0 */
+
+    qtest_quit(qts);
+}
+
+/* Hardlock array at 0xA0: read = test-and-set, write 0 = release */
+static void test_hardlock(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+    hwaddr l0 = MB_BASE + K230_MB_HARDLOCK_BASE;
+
+    g_assert_cmpuint(qtest_readl(qts, l0), ==, 0);   /* acquired */
+    g_assert_cmpuint(qtest_readl(qts, l0), ==, 1);   /* busy */
+    qtest_writel(qts, l0, 0);                         /* release */
+    g_assert_cmpuint(qtest_readl(qts, l0), ==, 0);   /* acquired again */
+
+    /* independence + last lock */
+    g_assert_cmpuint(qtest_readl(qts, l0 + 4), ==, 0);
+    g_assert_cmpuint(qtest_readl(qts, l0), ==, 1);
+    g_assert_cmpuint(qtest_readl(qts,
+                     l0 + (K230_MB_HARDLOCK_MAX - 1) * 4), ==, 0);
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/k230-mailbox/doorbell_cpu2dsp", test_doorbell_cpu2dsp);
+    qtest_add_func("/k230-mailbox/doorbell_dsp2cpu", test_doorbell_dsp2cpu);
+    qtest_add_func("/k230-mailbox/hardlock", test_hardlock);
+
+    return g_test_run();
+}
-- 
2.43.0


Reply via email to