Add emulation of the Messaging Unit and wire it into the
i.MX8MP SoC model. The MU provides inter-processor
communication between the Cortex-A53 and Cortex-M7 cores on the
i.MX8MP SoC. It is used by the Linux remoteproc framework as a
mailbox for lifecycle management and message passing with the
Cortex-M7 in an AMP configuration.

Signed-off-by: Gaurav Sharma <[email protected]>
---
 docs/system/arm/imx8m.rst   |   1 +
 hw/arm/Kconfig              |   1 +
 hw/arm/fsl-imx8mp.c         |  39 +++++
 hw/misc/Kconfig             |   3 +
 hw/misc/imx8mp_mu.c         | 308 ++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build         |   1 +
 include/hw/arm/fsl-imx8mp.h |  10 ++
 include/hw/misc/imx8mp_mu.h |  53 +++++++
 8 files changed, 416 insertions(+)
 create mode 100644 hw/misc/imx8mp_mu.c
 create mode 100644 include/hw/misc/imx8mp_mu.h

diff --git a/docs/system/arm/imx8m.rst b/docs/system/arm/imx8m.rst
index 49c100c4b1..c482bf180f 100644
--- a/docs/system/arm/imx8m.rst
+++ b/docs/system/arm/imx8m.rst
@@ -28,6 +28,7 @@ following devices:
  * General Power Controller (GPC)
  * General Purpose Register (GPR)
  * System Reset Controller (SRC)
+ * Messaging Unit (MU)
 
 Boot options
 ------------
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 8e89e30c45..977e6a8e49 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -603,6 +603,7 @@ config FSL_IMX8MP
     select FSL_IMX8MP_GPC
     select FSL_IMX8MP_GPR
     select FSL_IMX8MP_SRC
+    select FSL_IMX8MP_MU
     select IMX
     select IMX_FEC
     select IMX_I2C
diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c
index 168fcb0334..eec83deced 100644
--- a/hw/arm/fsl-imx8mp.c
+++ b/hw/arm/fsl-imx8mp.c
@@ -210,6 +210,16 @@ static void fsl_imx8mp_init(Object *obj)
 
     object_initialize_child(obj, "src", &s->src, TYPE_IMX8MP_SRC);
 
+    /*
+     * MU instances: MU_1..MU_3, each with A/B endpoints
+     */
+    for (i = 0; i < FSL_IMX8MP_NUM_MU; i++) {
+        g_autofree char *name_a = g_strdup_printf("mu%d_a", i + 1);
+        g_autofree char *name_b = g_strdup_printf("mu%d_b", i + 1);
+        object_initialize_child(obj, name_a, &s->mu[i].a, TYPE_IMX8MP_MU);
+        object_initialize_child(obj, name_b, &s->mu[i].b, TYPE_IMX8MP_MU);
+    }
+
     for (i = 0; i < FSL_IMX8MP_NUM_UARTS; i++) {
         g_autofree char *name = g_strdup_printf("uart%d", i + 1);
         object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL);
@@ -495,6 +505,34 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error 
**errp)
         }
     }
 
+    /* MU */
+    static const int mu_mmio_map[FSL_IMX8MP_NUM_MU][2] = {
+    [0] = { FSL_IMX8MP_MU_1_A, FSL_IMX8MP_MU_1_B },
+    [1] = { FSL_IMX8MP_MU_2_A, FSL_IMX8MP_MU_2_B },
+    [2] = { FSL_IMX8MP_MU_3_A, FSL_IMX8MP_MU_3_B },
+    };
+
+    for (i = 0; i < FSL_IMX8MP_NUM_MU; i++) {
+        hwaddr addr_a = fsl_imx8mp_memmap[mu_mmio_map[i][0]].addr;
+        hwaddr addr_b = fsl_imx8mp_memmap[mu_mmio_map[i][1]].addr;
+
+        if (!sysbus_realize(SYS_BUS_DEVICE(&s->mu[i].a), errp)) {
+            return;
+        }
+        sysbus_mmio_map(SYS_BUS_DEVICE(&s->mu[i].a), 0, addr_a);
+
+        if (!sysbus_realize(SYS_BUS_DEVICE(&s->mu[i].b), errp)) {
+            return;
+        }
+        sysbus_mmio_map(SYS_BUS_DEVICE(&s->mu[i].b), 0, addr_b);
+
+        s->mu[i].a.peer = &s->mu[i].b;
+        s->mu[i].b.peer = &s->mu[i].a;
+    }
+
+    sysbus_connect_irq(SYS_BUS_DEVICE(&s->mu[0].a), 0,
+                       qdev_get_gpio_in(gicdev, FSL_IMX8MP_MU1_A_IRQ));
+
     /* I2Cs */
     for (i = 0; i < FSL_IMX8MP_NUM_I2CS; i++) {
         struct {
@@ -720,6 +758,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error 
**errp)
         case FSL_IMX8MP_ENET1:
         case FSL_IMX8MP_I2C1 ... FSL_IMX8MP_I2C6:
         case FSL_IMX8MP_IOMUXC_GPR:
+        case FSL_IMX8MP_MU_1_A ... FSL_IMX8MP_MU_3_B:
         case FSL_IMX8MP_OCRAM:
         case FSL_IMX8MP_PCIE1:
         case FSL_IMX8MP_PCIE_PHY1:
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 185bd6f62a..144a3f7d67 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -110,6 +110,9 @@ config FSL_IMX8MP_GPR
 config FSL_IMX8MP_SRC
     bool
 
+config FSL_IMX8MP_MU
+    bool
+
 config STM32_RCC
     bool
 
diff --git a/hw/misc/imx8mp_mu.c b/hw/misc/imx8mp_mu.c
new file mode 100644
index 0000000000..f03cb07e22
--- /dev/null
+++ b/hw/misc/imx8mp_mu.c
@@ -0,0 +1,308 @@
+/*
+ * NXP i.MX Messaging Unit (MU)
+ *
+ * Copyright (c) 2026, NXP Semiconductors
+ * Author: Gaurav Sharma <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+
+#include "hw/misc/imx8mp_mu.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
+
+
+#define MU_SR_RFn_V1(x)   (1u << (24 + (3 - (x))))
+#define MU_SR_TEn_V1(x)   (1u << (20 + (3 - (x))))
+#define MU_SR_GIPn_V1(x)  (1u << (28 + (3 - (x))))
+#define MU_CR_RIEn_V1(x)  (1u << (24 + (3 - (x))))
+#define MU_CR_TIEn_V1(x)  (1u << (20 + (3 - (x))))
+#define MU_CR_GIEn_V1(x)  (1u << (28 + (3 - (x))))
+#define MU_CR_GIRn_V1(x)  (1u << (16 + (3 - (x))))
+
+
+static inline IMX8MPMUState *imx8mp_mu_peer(IMX8MPMUState *s)
+{
+    return s->peer;
+}
+
+static void imx8mp_mu_update_irq(IMX8MPMUState *s)
+{
+
+    bool pending = false;
+    uint32_t sr = s->mu[MU_SR];
+    uint32_t cr = s->mu[MU_CR];
+
+    for (int i = 0; i < 4; i++) {
+        /* TX done interrupt (TEn + TIEn) */
+        if ((sr & MU_SR_TEn_V1(i)) && (cr & MU_CR_TIEn_V1(i))) {
+            pending = true;
+            break;
+         }
+
+        /* RX data interrupt (RFn + RIEn) */
+        if ((sr & MU_SR_RFn_V1(i)) && (cr & MU_CR_RIEn_V1(i))) {
+            pending = true;
+            break;
+        }
+
+        /* Doorbell interrupt (GIPn + GIEn) - keep for completeness */
+        if ((sr & MU_SR_GIPn_V1(i)) && (cr & MU_CR_GIEn_V1(i))) {
+            pending = true;
+            break;
+        }
+    }
+    qemu_set_irq(s->irq, pending);
+}
+
+static uint64_t imx8mp_mu_read(void *opaque, hwaddr offset, unsigned size)
+{
+    IMX8MPMUState *s = opaque;
+
+    if (size != 4) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "imx8mp_mu: invalid read size %u @0x%" HWADDR_PRIx "\n",
+                      size, offset);
+        return 0;
+    }
+
+    if (offset & 3) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "imx8mp_mu: unaligned read @0x%"
+              HWADDR_PRIx "\n", offset);
+        return 0;
+    }
+
+    if (offset < MU_RR0) {
+        unsigned tr = offset >> 2;
+        if (s->strict_access) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "imx8mp_mu: strict: read from TR%u"
+              " @0x%" HWADDR_PRIx "\n", tr, offset);
+            return 0;
+        }
+        return s->mu[MU_TR0 + tr];
+    }
+
+    if (offset >= MU_RR0 && offset < MU_SR) {
+        unsigned rr = (offset - MU_RR0) >> 2;
+        uint32_t v = s->mu[MU_RR0 + rr];
+    IMX8MPMUState *p = imx8mp_mu_peer(s);
+
+        if (s->strict_access && !(s->mu[MU_SR] & MU_SR_RFn_V1(rr))) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "imx8mp_mu: strict: read from empty RR%u"
+              " @0x%" HWADDR_PRIx "\n", rr, offset);
+            return 0;
+        }
+
+    if (s->mu[MU_SR] & MU_SR_RFn_V1(rr)) {
+            s->mu[MU_SR] &= ~MU_SR_RFn_V1(rr);
+            if (p) {
+                p->mu[MU_SR] |= MU_SR_TEn_V1(rr);
+                imx8mp_mu_update_irq(p);
+            }
+        }
+        imx8mp_mu_update_irq(s);
+        return v;
+    }
+
+    switch (offset) {
+    case MU_SR:
+        return s->mu[MU_SR];
+    case MU_CR:
+        return s->mu[MU_CR];
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "imx8mp_mu: bad read @0x%" HWADDR_PRIx "\n", offset);
+        return 0;
+    }
+}
+
+static void imx8mp_mu_write(void *opaque, hwaddr offset,
+                             uint64_t value, unsigned size)
+{
+    IMX8MPMUState *s = opaque;
+
+    if (size != 4) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "imx8mp_mu: invalid write size %u @0x%" HWADDR_PRIx "\n",
+                      size, offset);
+        return;
+    }
+
+    if (offset & 3) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "imx8mp_mu: unaligned write @0x%"
+                      HWADDR_PRIx "\n", offset);
+        return;
+    }
+
+    /* TR0..TR3 */
+    if (offset < MU_RR0) {
+        unsigned tr = offset >> 2;
+        uint32_t v = (uint32_t)value;
+        IMX8MPMUState *p = imx8mp_mu_peer(s);
+
+    if (s->strict_access && !(s->mu[MU_SR] & MU_SR_TEn_V1(tr))) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "imx8mp_mu: strict: write to non-empty"
+                          " TR%u @0x%" HWADDR_PRIx "\n",
+                          tr, offset);
+            return;
+        }
+
+        s->mu[MU_TR0 + tr] = v;
+
+        /*
+         * Writing TR consumes local TX-empty (TE=0) and delivers the word
+         * into peer RR while setting peer RX-full (RF=1).
+         *
+         */
+        s->mu[MU_SR] &= ~MU_SR_TEn_V1(tr); /* TE=0 */
+        if (p) {
+            p->mu[MU_RR0 + tr] = v;
+            p->mu[MU_SR] |= MU_SR_RFn_V1(tr); /* RF=1 */
+            imx8mp_mu_update_irq(p);
+        }
+
+        imx8mp_mu_update_irq(s);
+        return;
+    }
+
+    /* RR are read-only from this side */
+    if (offset >= MU_RR0 && offset < MU_SR) {
+        unsigned rr = (offset - MU_RR0) >> 2;
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "imx8mp_mu: write to RR%d ignored\n", rr);
+        return;
+    }
+
+    switch (offset) {
+    case MU_CR: {
+        uint32_t v = (uint32_t)value;
+        IMX8MPMUState *p = imx8mp_mu_peer(s);
+        uint32_t gir_mask = 0;
+
+        for (int i = 0; i < 4; i++) {
+            gir_mask |= MU_CR_GIRn_V1(i);
+        }
+
+        if (p) {
+            for (int i = 0; i < 4; i++) {
+                if (v & MU_CR_GIRn_V1(i)) {
+                    /* Set peer doorbell pending */
+                    p->mu[MU_SR] |= MU_SR_GIPn_V1(i);
+                }
+            }
+            imx8mp_mu_update_irq(p);
+        }
+
+        /*
+         * Model GIRn as write-to-trigger (self-clearing) and keep other CR
+         * bits latched.
+         */
+        v &= ~gir_mask;
+
+        s->mu[MU_CR] = v;
+        imx8mp_mu_update_irq(s);
+        return;
+    }
+
+    case MU_SR:{
+
+        /* Write-1-to-clear for doorbell bits */
+        uint32_t v = (uint32_t)value;
+        for (int i = 0; i < 4; i++) {
+            if (v & MU_SR_GIPn_V1(i)) {
+                s->mu[MU_SR] &= ~MU_SR_GIPn_V1(i);
+            }
+        }
+        imx8mp_mu_update_irq(s);
+        return;
+    }
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "imx8mp_mu: bad write @0x%" HWADDR_PRIx
+              " = 0x%08" PRIx64 "\n", offset, value);
+        return;
+    }
+}
+
+static void imx8mp_mu_reset(DeviceState *dev)
+{
+    IMX8MPMUState *s = IMX8MP_MU(dev);
+    memset(s->mu, 0, sizeof(s->mu));
+
+    s->mu[MU_SR] = 0x00F00080;
+
+    imx8mp_mu_update_irq(s);
+}
+
+static const MemoryRegionOps imx8mp_mu_ops = {
+    .read = imx8mp_mu_read,
+    .write = imx8mp_mu_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    .unaligned = false,
+    },
+};
+
+static const VMStateDescription imx8mp_mu_vmstate = {
+    .name = TYPE_IMX8MP_MU,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+    VMSTATE_UINT32_ARRAY(mu, IMX8MPMUState, MU_MAX),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void imx8mp_mu_init(Object *obj)
+{
+    IMX8MPMUState *s = IMX8MP_MU(obj);
+    SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+
+    /* Default to strict hardware-like access semantics. */
+    s->strict_access = true;
+
+    memory_region_init(&s->mmio.container, obj, TYPE_IMX8MP_MU,
+                       IMX8MP_MU_MMIO_SIZE);
+
+    /* Implemented subset as an IO subregion at offset 0 */
+    memory_region_init_io(&s->mmio.regs, obj, &imx8mp_mu_ops, s,
+                          TYPE_IMX8MP_MU ".regs", sizeof(s->mu));
+    memory_region_add_subregion(&s->mmio.container, 0, &s->mmio.regs);
+
+    sysbus_init_mmio(sd, &s->mmio.container);
+    sysbus_init_irq(sd, &s->irq);
+}
+
+static void imx8mp_mu_class_init(ObjectClass *oc, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+
+    device_class_set_legacy_reset(dc, imx8mp_mu_reset);
+    dc->vmsd = &imx8mp_mu_vmstate;
+    dc->desc  = "i.MX 8M Plus Messaging Unit";
+}
+
+static const TypeInfo imx8mp_mu_type_info[] = {
+    {
+        .name = TYPE_IMX8MP_MU,
+        .parent = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(IMX8MPMUState),
+        .instance_init = imx8mp_mu_init,
+        .class_init = imx8mp_mu_class_init,
+    }
+};
+
+DEFINE_TYPES(imx8mp_mu_type_info);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 174aed40b6..43b5696865 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -62,6 +62,7 @@ system_ss.add(when: 'CONFIG_FSL_IMX8MP_CCM', if_true: 
files('imx8mp_ccm.c'))
 system_ss.add(when: 'CONFIG_FSL_IMX8MP_GPC', if_true: files('imx8mp_gpc.c'))
 system_ss.add(when: 'CONFIG_FSL_IMX8MP_GPR', if_true: files('imx8mp_gpr.c'))
 system_ss.add(when: 'CONFIG_FSL_IMX8MP_SRC', if_true: files('imx8mp_src.c'))
+system_ss.add(when: 'CONFIG_FSL_IMX8MP_MU', if_true: files('imx8mp_mu.c'))
 system_ss.add(when: 'CONFIG_IMX', if_true: files(
   'imx25_ccm.c',
   'imx31_ccm.c',
diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h
index c6c133cc05..94d198b932 100644
--- a/include/hw/arm/fsl-imx8mp.h
+++ b/include/hw/arm/fsl-imx8mp.h
@@ -20,6 +20,7 @@
 #include "hw/misc/imx8mp_gpc.h"
 #include "hw/misc/imx8mp_gpr.h"
 #include "hw/misc/imx8mp_src.h"
+#include "hw/misc/imx8mp_mu.h"
 #include "hw/net/imx_fec.h"
 #include "hw/core/or-irq.h"
 #include "hw/pci-host/designware.h"
@@ -39,6 +40,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mpState, FSL_IMX8MP)
 #define FSL_IMX8MP_RAM_START        0x40000000
 #define FSL_IMX8MP_RAM_SIZE_MAX     (8 * GiB)
 
+#define FSL_IMX8MP_MU1_A_IRQ  88
+
 enum FslImx8mpConfiguration {
     FSL_IMX8MP_NUM_CPUS         = 4,
     FSL_IMX8MP_NUM_ECSPIS       = 3,
@@ -46,12 +49,18 @@ enum FslImx8mpConfiguration {
     FSL_IMX8MP_NUM_GPTS         = 6,
     FSL_IMX8MP_NUM_I2CS         = 6,
     FSL_IMX8MP_NUM_IRQS         = 160,
+    FSL_IMX8MP_NUM_MU           = 3,
     FSL_IMX8MP_NUM_UARTS        = 4,
     FSL_IMX8MP_NUM_USBS         = 2,
     FSL_IMX8MP_NUM_USDHCS       = 3,
     FSL_IMX8MP_NUM_WDTS         = 3,
 };
 
+typedef struct IMX8MPMUPair {
+    IMX8MPMUState a;
+    IMX8MPMUState b;
+} IMX8MPMUPair;
+
 struct FslImx8mpState {
     SysBusDevice   parent_obj;
 
@@ -65,6 +74,7 @@ struct FslImx8mpState {
     IMX8MPAnalogState  analog;
     IMX7SNVSState      snvs;
     IMXSPIState        spi[FSL_IMX8MP_NUM_ECSPIS];
+    IMX8MPMUPair       mu[FSL_IMX8MP_NUM_MU];
     FslImx8mpSrcState  src;
     IMXI2CState        i2c[FSL_IMX8MP_NUM_I2CS];
     IMXSerialState     uart[FSL_IMX8MP_NUM_UARTS];
diff --git a/include/hw/misc/imx8mp_mu.h b/include/hw/misc/imx8mp_mu.h
new file mode 100644
index 0000000000..4801708402
--- /dev/null
+++ b/include/hw/misc/imx8mp_mu.h
@@ -0,0 +1,53 @@
+/*
+ * NXP i.MX 8M Plus  Messaging Unit (MU)
+ *
+ * Copyright (c) 2026, NXP Semiconductors
+ * Author: Gaurav Sharma <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#ifndef IMX8MP_MU_H
+#define IMX8MP_MU_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define IMX8MP_MU_MMIO_SIZE 0x10000
+
+enum IMX8MPMURegisters {
+    MU_TR0     = 0x000 / 4,
+    MU_TR1     = 0x004 / 4,
+    MU_TR2     = 0x008 / 4,
+    MU_TR3     = 0x00C / 4,
+    MU_RR0     = 0x010 / 4,
+    MU_RR1     = 0x014 / 4,
+    MU_RR2     = 0x018 / 4,
+    MU_RR3     = 0x01C / 4,
+    MU_SR      = 0x020 / 4,
+    MU_CR      = 0x024 / 4,
+    MU_MAX,
+};
+
+#define TYPE_IMX8MP_MU "imx8mp-mu"
+OBJECT_DECLARE_SIMPLE_TYPE(IMX8MPMUState, IMX8MP_MU)
+
+typedef struct IMX8MPMUState {
+    SysBusDevice parent_obj;
+
+    struct {
+        MemoryRegion container;
+        MemoryRegion regs;
+    } mmio;
+
+    qemu_irq irq;
+
+    struct IMX8MPMUState *peer;
+    uint32_t mu[MU_MAX];
+
+    bool strict_access;
+
+} IMX8MPMUState;
+
+#endif /* IMX8MP_MU_H */
-- 
2.34.1


Reply via email to