All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kyle Fox <kylefoxaustin.github@gmail.com>
To: qemu-devel@nongnu.org
Cc: Kyle Fox <kylefoxaustin.github@gmail.com>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Peter Maydell <peter.maydell@linaro.org>,
	qemu-arm@nongnu.org (open list:i.MX31 (kzm))
Subject: [PATCH 07/16] hw/misc: add i.MX Messaging Unit (MU v2)
Date: Wed, 19 Aug 2026 21:48:25 -0500	[thread overview]
Message-ID: <20260820024834.3286721-8-kylefoxaustin.github@gmail.com> (raw)
In-Reply-To: <20260820024834.3286721-1-kylefoxaustin.github@gmail.com>

The i.MX Messaging Unit (V2 register layout): the mailbox that carries
SCMI between the A55 cluster and the M33 System Manager. Models
CR/SR/GCR/GSR/GIER, the TR/RR data registers and the GCR.GIRn/GSR.GIPn
doorbell. Two endpoints pair through a peer QOM link: a TR write is
delivered into the peer's RR and the TX handshake completes when the
peer reads RR, so the real, unmodified SM firmware answers Linux's SCMI
traffic over the emulated mailbox.

Signed-off-by: Kyle Fox <kylefoxaustin.github@gmail.com>
---
 hw/misc/Kconfig          |   3 +
 hw/misc/imx_mu.c         | 454 +++++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build      |   1 +
 hw/misc/trace-events     |   4 +
 include/hw/misc/imx_mu.h | 144 +++++++++++++
 5 files changed, 606 insertions(+)
 create mode 100644 hw/misc/imx_mu.c
 create mode 100644 include/hw/misc/imx_mu.h

diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index b8860dd3e77..8645ca11f3f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -261,3 +261,6 @@ config AXIADO_CLK
     bool
 
 source macio/Kconfig
+
+config IMX_MU
+    bool
diff --git a/hw/misc/imx_mu.c b/hw/misc/imx_mu.c
new file mode 100644
index 00000000000..c9f372a8726
--- /dev/null
+++ b/hw/misc/imx_mu.c
@@ -0,0 +1,454 @@
+/*
+ * NXP i.MX Messaging Unit (MU) device model - V2 register layout
+ *
+ * Copyright (c) 2026, Kyle Fox
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Implements the subset of the V2 MU register set exercised by U-Boot
+ * SPL's imx_mu_init_generic() probe path and by SCMI mailbox traffic:
+ * CR/SR/GCR/GSR/GIER/TCR/TSR/RCR/RSR + 4 TR/RR data registers, plus
+ * a doorbell mechanism via GCR.GIRn / GSR.GIPn.
+ *
+ * Models enough for the SPL probe to succeed and for the SM to
+ * handshake on doorbells. No
+ * cross-domain interrupt routing, no flag-bit semantics beyond
+ * read/write-back, no per-channel TX/RX FIFO depth modelling.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/imx_mu.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+static void imx_mu_update_irq(IMXMUState *s);
+
+static int imx_mu_post_load(void *opaque, int version_id)
+{
+    /* Recompute the IRQ line level from the restored register state. */
+    imx_mu_update_irq(opaque);
+    return 0;
+}
+
+static const VMStateDescription vmstate_imx_mu = {
+    .name = TYPE_IMX_MU,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = imx_mu_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(cr, IMXMUState),
+        VMSTATE_UINT32(sr, IMXMUState),
+        VMSTATE_UINT32(fcr, IMXMUState),
+        VMSTATE_UINT32(fsr, IMXMUState),
+        VMSTATE_UINT32(gier, IMXMUState),
+        VMSTATE_UINT32(gcr, IMXMUState),
+        VMSTATE_UINT32(gsr, IMXMUState),
+        VMSTATE_UINT32(tcr, IMXMUState),
+        VMSTATE_UINT32(tsr, IMXMUState),
+        VMSTATE_UINT32(rcr, IMXMUState),
+        VMSTATE_UINT32(rsr, IMXMUState),
+        VMSTATE_UINT32_ARRAY(tr, IMXMUState, IMX_MU_NUM_CHANNELS),
+        VMSTATE_UINT32_ARRAY(rr, IMXMUState, IMX_MU_NUM_CHANNELS),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+/*
+ * Recompute the IRQ line. V2 layout: each xSR pending bit ANDed with
+ * the matching xCR enable bit; any one of those produces an interrupt.
+ * GP-IRQ comes from (GSR.GIPn & GIER.GIEn) across the channels in use.
+ */
+static void imx_mu_update_irq(IMXMUState *s)
+{
+    uint32_t mask = (1u << IMX_MU_NUM_CHANNELS) - 1u;
+    bool level =
+        ((s->gsr & s->gier & mask) != 0) ||
+        ((s->tsr & s->tcr  & mask) != 0) ||
+        ((s->rsr & s->rcr  & mask) != 0);
+
+    qemu_set_irq(s->irq, level);
+}
+
+static void imx_mu_reset_state(IMXMUState *s)
+{
+    s->cr   = 0;
+    s->sr   = 0;
+    s->fcr  = 0;
+    s->fsr  = 0;
+    s->gier = 0;
+    s->gcr  = 0;
+    s->gsr  = 0;
+    s->tcr  = 0;
+    /* All TX slots come up empty - TEn bits set. */
+    s->tsr  = (1u << IMX_MU_NUM_CHANNELS) - 1u;
+    s->rcr  = 0;
+    s->rsr  = 0;
+    memset(s->tr, 0, sizeof(s->tr));
+    memset(s->rr, 0, sizeof(s->rr));
+}
+
+static void imx_mu_reset_at_boot_hold(Object *obj, ResetType type)
+{
+    IMXMUState *s = IMX_MU(obj);
+
+    imx_mu_reset_state(s);
+    imx_mu_update_irq(s);
+}
+
+void imx_mu_assert_gip(IMXMUState *s, unsigned int idx)
+{
+    if (idx >= IMX_MU_NUM_CHANNELS) {
+        return;
+    }
+    trace_imx_mu_gip(idx);
+    s->gsr |= IMX_MU_V2_BIT(idx);
+    imx_mu_update_irq(s);
+}
+
+void imx_mu_set_doorbell_handler(IMXMUState *s,
+                                 IMXMUDoorbellHandler handler,
+                                 void *opaque)
+{
+    s->doorbell_handler = handler;
+    s->doorbell_opaque  = opaque;
+}
+
+void imx_mu_set_tr_write_handler(IMXMUState *s,
+                                 IMXMUTRWriteHandler handler,
+                                 void *opaque)
+{
+    s->tr_write_handler = handler;
+    s->tr_write_opaque  = opaque;
+}
+
+void imx_mu_deliver_rr(IMXMUState *s, unsigned int idx, uint32_t value)
+{
+    if (idx >= IMX_MU_NUM_CHANNELS) {
+        return;
+    }
+    trace_imx_mu_rr_deliver(idx, value);
+    s->rr[idx]  = value;
+    s->rsr     |= IMX_MU_V2_BIT(idx);
+    imx_mu_update_irq(s);
+}
+
+static uint64_t imx_mu_read(void *opaque, hwaddr offset, unsigned size)
+{
+    IMXMUState *s = opaque;
+    uint64_t value = 0;
+
+    if (offset >= IMX_MU_TR_BASE &&
+        offset < IMX_MU_TR_BASE + IMX_MU_NUM_CHANNELS * 4) {
+        value = s->tr[(offset - IMX_MU_TR_BASE) / 4];
+        return value;
+    }
+    if (offset >= IMX_MU_RR_BASE &&
+        offset < IMX_MU_RR_BASE + IMX_MU_NUM_CHANNELS * 4) {
+        unsigned idx = (offset - IMX_MU_RR_BASE) / 4;
+        value = s->rr[idx];
+        /*
+         * Reading RR[n] clears RSR.RFn (the receive-full status bit)
+         * so the next sender can re-queue. The SCMI transport doesn't
+         * rely on this, but it matches the documented HW
+         * behaviour and lets the U-Boot probe drain pending data.
+         *
+         * For a peer-linked MU this completes the TX handshake: the word
+         * was delivered into RR[n] by the peer's TR[n] write (which left
+         * the peer's TSR.TEn clear), so draining it re-asserts the peer's
+         * TX-empty and lets a blocked mbox send finish.
+         */
+        s->rsr &= ~IMX_MU_V2_BIT(idx);
+        if (s->peer) {
+            s->peer->tsr |= IMX_MU_V2_BIT(idx);
+            imx_mu_update_irq(s->peer);
+        }
+        imx_mu_update_irq(s);
+        return value;
+    }
+
+    switch (offset) {
+    case IMX_MU_VER:
+        /* Plausible V2 version. Linux/U-Boot do not branch on this. */
+        value = 0x00020000;
+        break;
+    case IMX_MU_PAR:
+        /* TR count in [7:0], RR count in [15:8] (V2 encoding). */
+        value = ((uint32_t)IMX_MU_NUM_CHANNELS) |
+                ((uint32_t)IMX_MU_NUM_CHANNELS << 8);
+        break;
+    case IMX_MU_CR:
+        value = s->cr;
+        break;
+    case IMX_MU_SR:
+        value = s->sr;
+        break;
+    case IMX_MU_FCR:
+        value = s->fcr;
+        break;
+    case IMX_MU_FSR:
+        value = s->fsr;
+        break;
+    case IMX_MU_GIER:
+        value = s->gier;
+        break;
+    case IMX_MU_GCR:
+        value = s->gcr;
+        break;
+    case IMX_MU_GSR:
+        value = s->gsr;
+        break;
+    case IMX_MU_TCR:
+        value = s->tcr;
+        break;
+    case IMX_MU_TSR:
+        value = s->tsr;
+        break;
+    case IMX_MU_RCR:
+        value = s->rcr;
+        break;
+    case IMX_MU_RSR:
+        value = s->rsr;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+        break;
+    }
+    return value;
+}
+
+static void imx_mu_write(void *opaque, hwaddr offset,
+                         uint64_t value, unsigned size)
+{
+    IMXMUState *s = opaque;
+
+    if (offset >= IMX_MU_TR_BASE &&
+        offset < IMX_MU_TR_BASE + IMX_MU_NUM_CHANNELS * 4) {
+        unsigned idx = (offset - IMX_MU_TR_BASE) / 4;
+        s->tr[idx] = value;
+        trace_imx_mu_tr_write(idx, value);
+        /*
+         * Writing TR[n] clears TSR.TEn (TX empty). Three delivery modes:
+         *  - TR-write handler (a responder, e.g. the ELE server): consumes
+         *    synchronously and we re-set TEn so the next write succeeds
+         *    without polling.
+         *  - peer linked (the other side of a real MU, e.g. the A55<->M7
+         *    rpmsg channel on MU7): deliver the word into the peer's RR[n]
+         *    and raise its RX interrupt. TEn stays clear until the peer
+         *    reads RR[n] (see the RR read path), which is the real MU TX
+         *    full/empty handshake the imx-mailbox driver waits on.
+         *  - neither (unattached MU): the slot stays "full" forever, which
+         *    is correct for a mailbox with nothing on the far side.
+         */
+        s->tsr &= ~IMX_MU_V2_BIT(idx);
+        if (s->tr_write_handler) {
+            s->tr_write_handler(s->tr_write_opaque, idx, value);
+            s->tsr |= IMX_MU_V2_BIT(idx);
+        } else if (s->peer) {
+            imx_mu_deliver_rr(s->peer, idx, (uint32_t)value);
+        }
+        imx_mu_update_irq(s);
+        return;
+    }
+    if (offset >= IMX_MU_RR_BASE &&
+        offset < IMX_MU_RR_BASE + IMX_MU_NUM_CHANNELS * 4) {
+        /* RR is RO from the guest's perspective. */
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: write to read-only RR at 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+        return;
+    }
+
+    switch (offset) {
+    case IMX_MU_VER:
+    case IMX_MU_PAR:
+        /* Read-only. Silent ignore matches HW. */
+        break;
+
+    case IMX_MU_CR:
+        if (value & IMX_MU_CR_RST) {
+            imx_mu_reset_state(s);
+            imx_mu_update_irq(s);
+            return;
+        }
+        s->cr = value;
+        break;
+
+    case IMX_MU_SR:
+        /*
+         * Per the kernel/U-Boot drivers, SR bits in V2 are read-only
+         * status that the model manages. Writing has no effect.
+         */
+        break;
+
+    case IMX_MU_FCR:
+        /*
+         * Flag-update register. On hardware a written flag bit is reflected
+         * to the peer's FSR; the SCMI transport does not use these flags, so
+         * we just store the value (no peer reflection or interrupt modelled).
+         */
+        s->fcr = value;
+        break;
+
+    case IMX_MU_FSR:
+        /* Write-1-to-clear of latched flag-status bits. */
+        s->fsr &= ~value;
+        imx_mu_update_irq(s);
+        break;
+
+    case IMX_MU_GIER:
+        s->gier = value;
+        imx_mu_update_irq(s);
+        break;
+
+    case IMX_MU_GCR: {
+        /*
+         * GCR.GIRn writes are doorbell triggers. Detect 0->1 transitions
+         * and, for each newly-asserted channel, deliver the doorbell. The
+         * bit is auto-cleared afterwards (write-1-to-trigger pulse),
+         * mirroring real HW where the request clears once the peer ACKs.
+         *
+         * Two delivery modes:
+         *  - peer linked (the other side of a real MU): latch the matching
+         *    GSR.GIPn on the peer and recompute the peer's IRQ. GIPn latches
+         *    regardless of the peer's GIER, so a doorbell that arrives before
+         *    the peer enables GIER.GIEn fires the moment it does (the peer's
+         *    GIER write recomputes the IRQ) - real pending-vs-enable HW.
+         *  - doorbell handler (a responder, e.g. the ELE server): invoke it
+         *    synchronously to consume the request.
+         */
+        uint32_t mask  = (1u << IMX_MU_NUM_CHANNELS) - 1u;
+        uint32_t newly = (value & ~s->gcr) & mask;
+        s->gcr = value;
+        for (unsigned i = 0; i < IMX_MU_NUM_CHANNELS; i++) {
+            if (!(newly & IMX_MU_V2_BIT(i))) {
+                continue;
+            }
+            trace_imx_mu_doorbell(i);
+            if (s->peer) {
+                s->peer->gsr |= IMX_MU_V2_BIT(i);
+                imx_mu_update_irq(s->peer);
+                s->gcr &= ~IMX_MU_V2_BIT(i);
+            } else if (s->doorbell_handler) {
+                s->doorbell_handler(s->doorbell_opaque, i);
+                s->gcr &= ~IMX_MU_V2_BIT(i);
+            }
+        }
+        imx_mu_update_irq(s);
+        break;
+    }
+
+    case IMX_MU_GSR:
+        /* Write-1-to-clear of GIP bits. */
+        s->gsr &= ~value;
+        imx_mu_update_irq(s);
+        break;
+
+    case IMX_MU_TCR:
+        s->tcr = value;
+        imx_mu_update_irq(s);
+        break;
+
+    case IMX_MU_TSR:
+        /* W1C of TX status. */
+        s->tsr &= ~value;
+        imx_mu_update_irq(s);
+        break;
+
+    case IMX_MU_RCR:
+        s->rcr = value;
+        imx_mu_update_irq(s);
+        break;
+
+    case IMX_MU_RSR:
+        /* W1C of RX status. */
+        s->rsr &= ~value;
+        imx_mu_update_irq(s);
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: bad write offset 0x%" HWADDR_PRIx
+                      " value 0x%" PRIx64 "\n",
+                      __func__, offset, value);
+        break;
+    }
+}
+
+static const MemoryRegionOps imx_mu_ops = {
+    .read = imx_mu_read,
+    .write = imx_mu_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+/*
+ * "peer" accepts any TYPE_IMX_MU and is settable at any time: the SM/M7 MUB
+ * endpoint is only created (and hence linkable) once its M-core is realized,
+ * so the SoC establishes the link after realize. A non-NULL check is what
+ * makes the link writable; the type is already enforced by the link itself.
+ */
+static void imx_mu_peer_check(const Object *obj, const char *name,
+                              Object *val, Error **errp)
+{
+}
+
+static void imx_mu_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    IMXMUState *s = IMX_MU(obj);
+
+    memory_region_init_io(&s->iomem, obj, &imx_mu_ops, s,
+                          TYPE_IMX_MU, IMX_MU_REG_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+    sysbus_init_irq(sbd, &s->irq);
+
+    /*
+     * "peer" links the two endpoints (MUA/MUB) of one physical MU. It is a
+     * weak link - both endpoints are owned by the SoC container, so neither
+     * refs the other - and is settable after realize, because the SM/M7 MUB
+     * side is only created once the M-core it faces exists. That is why this
+     * uses object_property_add_link() rather than a static DEFINE_PROP_LINK
+     * (which forbids setting a link once the device is realized).
+     */
+    object_property_add_link(obj, "peer", TYPE_IMX_MU,
+                             (Object **)&s->peer, imx_mu_peer_check, 0);
+}
+
+static void imx_mu_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &vmstate_imx_mu;
+    rc->phases.hold = imx_mu_reset_at_boot_hold;
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    dc->desc = "NXP i.MX Messaging Unit (V2)";
+}
+
+static const TypeInfo imx_mu_info = {
+    .name           = TYPE_IMX_MU,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(IMXMUState),
+    .instance_init  = imx_mu_init,
+    .class_init     = imx_mu_class_init,
+};
+
+static void imx_mu_register_types(void)
+{
+    type_register_static(&imx_mu_info);
+}
+
+type_init(imx_mu_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index e86d9ad6b39..9d0e3ed220d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -171,3 +171,4 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
 system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
 
 system_ss.add(when: 'CONFIG_AXIADO_CLK', if_true: files('axiado_clk.c'))
+system_ss.add(when: 'CONFIG_IMX_MU', if_true: files('imx_mu.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c9a868b3efb..01b0b275f7f 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -442,3 +442,7 @@ 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"
+imx_mu_tr_write(unsigned idx, uint32_t val) "TR[%u] <- 0x%08x"
+imx_mu_rr_deliver(unsigned idx, uint32_t val) "RR[%u] <- 0x%08x"
+imx_mu_doorbell(unsigned idx) "doorbell GIR channel %u"
+imx_mu_gip(unsigned idx) "GIP assert channel %u"
diff --git a/include/hw/misc/imx_mu.h b/include/hw/misc/imx_mu.h
new file mode 100644
index 00000000000..e308ebf70ea
--- /dev/null
+++ b/include/hw/misc/imx_mu.h
@@ -0,0 +1,144 @@
+/*
+ * NXP i.MX Messaging Unit (MU) device model - V2 register layout
+ *
+ * Copyright (c) 2026, Kyle Fox
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Models the V2 MU register layout used by i.MX 95 (compatible
+ * "fsl,imx95-mu") and other recent NXP SoCs. The MU is the
+ * messaging channel between the Cortex-A55 cluster and the Cortex-M33
+ * System Manager (and ELE, V2X, camera-mix, etc., depending on which
+ * instance); it carries SCMI traffic for clock / pinctrl / power-
+ * domain operations.
+ */
+
+#ifndef IMX_MU_H
+#define IMX_MU_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_IMX_MU "imx.mu"
+OBJECT_DECLARE_SIMPLE_TYPE(IMXMUState, IMX_MU)
+
+#define IMX_MU_REG_SIZE         0x1000
+#define IMX_MU_NUM_CHANNELS     4       /* TR[0..3] / RR[0..3] */
+
+/*
+ * V2 register offsets. Source: NXP U-Boot imx_mu_cfg_imx95 in
+ * U-Boot drivers/mailbox/imx-mailbox.c.
+ */
+#define IMX_MU_VER              0x000   /* version (RO) */
+#define IMX_MU_PAR              0x004   /* parameter (RO; TR/RR counts) */
+#define IMX_MU_CR               0x008   /* control */
+#define IMX_MU_SR               0x00C   /* status */
+#define IMX_MU_FCR              0x100   /* flag control */
+#define IMX_MU_FSR              0x104   /* flag status */
+#define IMX_MU_GIER             0x110   /* general-purpose IRQ enable */
+#define IMX_MU_GCR              0x114   /* general-purpose control */
+#define IMX_MU_GSR              0x118   /* general-purpose status (GIP bits) */
+#define IMX_MU_TCR              0x120   /* TX IRQ enable */
+#define IMX_MU_TSR              0x124   /* TX status (TEn = TX empty) */
+#define IMX_MU_RCR              0x128   /* RX IRQ enable */
+#define IMX_MU_RSR              0x12C   /* RX status (RFn = RX full) */
+#define IMX_MU_TR_BASE          0x200   /* TR[0..15] (TX data) */
+#define IMX_MU_RR_BASE          0x280   /* RR[0..15] (RX data) */
+
+/* Helpers for V2 bit numbering (one bit per channel index, starting at 0). */
+#define IMX_MU_V2_BIT(idx)      (1u << (idx))
+
+/* CR.RST clears all state when written (V2 bit 0). */
+#define IMX_MU_CR_RST           BIT(0)
+
+/*
+ * Optional callback invoked when the guest writes a 0->1 transition
+ * on any GCR.GIRn bit (a doorbell trigger from the agent side). A
+ * responder (e.g. the ELE server) registers itself here to process
+ * inbound messages. Unset by default; the model functions as a plain
+ * register file when no handler is registered.
+ */
+typedef void (*IMXMUDoorbellHandler)(void *opaque, unsigned int idx);
+
+/*
+ * Optional callback invoked when the guest writes to a TR[idx] register.
+ * The ELE responder stub uses this to accumulate incoming ELE-protocol
+ * message words and react when a full message has arrived. Unset by
+ * default; the model is a plain register file with no consumer.
+ */
+typedef void (*IMXMUTRWriteHandler)(void *opaque, unsigned int idx,
+                                    uint32_t value);
+
+struct IMXMUState {
+    SysBusDevice    parent_obj;
+
+    MemoryRegion    iomem;
+    qemu_irq        irq;
+
+    /*
+     * Writable register state. Read-only registers (VER, PAR) are
+     * computed at read time from the configured channel count.
+     */
+    uint32_t        cr;
+    uint32_t        sr;
+    uint32_t        fcr;
+    uint32_t        fsr;
+    uint32_t        gier;
+    uint32_t        gcr;
+    uint32_t        gsr;
+    uint32_t        tcr;
+    uint32_t        tsr;
+    uint32_t        rcr;
+    uint32_t        rsr;
+    uint32_t        tr[IMX_MU_NUM_CHANNELS];
+    uint32_t        rr[IMX_MU_NUM_CHANNELS];
+
+    /* Doorbell forwarding (see typedef above). */
+    IMXMUDoorbellHandler doorbell_handler;
+    void                *doorbell_opaque;
+
+    /* TR-write forwarding (see typedef above). */
+    IMXMUTRWriteHandler  tr_write_handler;
+    void                *tr_write_opaque;
+
+    /*
+     * Optional peer MU endpoint (the other side of a real hardware MU).
+     * When set, a GCR.GIRn doorbell trigger on this side latches the
+     * matching GSR.GIPn on the peer (raising the peer's IRQ once the peer
+     * enables GIER.GIEn), instead of invoking doorbell_handler. This models
+     * the A55-side (MUA) <-> M33-side (MUB) cross-connect used to let the
+     * real SM firmware service the A55's SCMI traffic. Set via the "peer"
+     * QOM link property (registered in imx_mu_init()).
+     */
+    IMXMUState          *peer;
+};
+
+/*
+ * Register a doorbell handler. Replaces any previously registered
+ * handler. Call with handler = NULL to deregister.
+ */
+void imx_mu_set_doorbell_handler(IMXMUState *s,
+                                 IMXMUDoorbellHandler handler,
+                                 void *opaque);
+
+/* Register a TR-write handler. NULL to deregister. */
+void imx_mu_set_tr_write_handler(IMXMUState *s,
+                                 IMXMUTRWriteHandler handler,
+                                 void *opaque);
+
+/*
+ * Deliver a response word into RR[idx] and assert RSR.RFn so the
+ * agent's mu_hal_receivemsg() poll loop exits. Used by responder
+ * stubs (e.g. ELE) to push a message back to the agent.
+ */
+void imx_mu_deliver_rr(IMXMUState *s, unsigned int idx, uint32_t value);
+
+/*
+ * External hook: peripheral code (e.g. a responder) can call
+ * this to assert a GP-interrupt-pending bit on a particular channel,
+ * which surfaces as GSR.GIPn and (if GIER.GIEn is set) raises the IRQ.
+ * Used by the response side of a doorbell handshake.
+ */
+void imx_mu_assert_gip(IMXMUState *s, unsigned int idx);
+
+#endif /* IMX_MU_H */
-- 
2.34.1



  parent reply	other threads:[~2026-08-20  2:50 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-20  2:48 [PATCH 00/16] hw/arm: add the NXP i.MX 95 EVK machine Kyle Fox
2026-08-20  2:48 ` [PATCH 01/16] hw/sd/sdhci: add i.MX uSDHC SDCLK_AUTO_GATE and NO_SDMA_BOUNDARY quirks Kyle Fox
2026-08-20  2:48 ` [PATCH 02/16] hw/arm/boot: let a board preset initrd_start Kyle Fox
2026-08-20  2:48 ` [PATCH 03/16] target/arm: opt-in align-down for a misaligned PMSAv7 MPU RBAR Kyle Fox
2026-08-20  2:48 ` [PATCH 04/16] hw/arm/armv7m: forward pmsav7-rbar-align-down to the CPU Kyle Fox
2026-08-20  2:48 ` [PATCH 05/16] hw/char: add i.MX LPUART Kyle Fox
2026-08-20  2:48 ` [PATCH 06/16] hw/i2c: add i.MX LPI2C Kyle Fox
2026-08-20  2:48 ` Kyle Fox [this message]
2026-08-20  2:48 ` [PATCH 08/16] hw/misc: add NXP EdgeLock Enclave (ELE) responder Kyle Fox
2026-08-20  2:48 ` [PATCH 09/16] hw/timer: add i.MX 95 system counter Kyle Fox
2026-08-20  2:48 ` [PATCH 10/16] hw/misc: add i.MX 95 watchdog Kyle Fox
2026-08-20  2:48 ` [PATCH 11/16] hw/misc: add i.MX 95 ANATOP/AONMIX/GPC/SRC power and clock blocks Kyle Fox
2026-08-20  2:48 ` [PATCH 12/16] hw/misc: add i.MX 95 PMIC (PF09/PF53/PCAL6408A) and xcache controllers Kyle Fox
2026-08-20  2:48 ` [PATCH 13/16] hw/misc: add i.MX 95 DPU command-sequencer stub (headless) Kyle Fox
2026-08-20  2:48 ` [PATCH 14/16] hw/arm: add i.MX 95 SoC container (fsl-imx95) Kyle Fox
2026-08-20  2:48 ` [PATCH 15/16] hw/arm: add i.MX 95 19x19 EVK board Kyle Fox
2026-08-20  2:48 ` [PATCH 16/16] docs, MAINTAINERS, tests/functional: add i.MX 95 EVK Kyle Fox

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260820024834.3286721-8-kylefoxaustin.github@gmail.com \
    --to=kylefoxaustin.github@gmail.com \
    --cc=pbonzini@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.