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
