[PATCH 15/16] hw/arm: add i.MX 95 19x19 EVK board

Kyle Fox <[email protected]>
Newsgroups org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
The i.MX 95 19x19 EVK board (machine type imx95-19x19-evk).
Instantiates the SoC, sets up the LPDDR5 DRAM, and provides the direct
-kernel/-dtb/-initrd Linux boot path alongside the System Manager
firmware loaded on the M33. This is the user-facing machine type.

Signed-off-by: Kyle Fox <[email protected]>
---
 hw/arm/Kconfig     |   7 +++
 hw/arm/imx95-evk.c | 136 +++++++++++++++++++++++++++++++++++++++++++++
 hw/arm/meson.build |   1 +
 3 files changed, 144 insertions(+)
 create mode 100644 hw/arm/imx95-evk.c

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 7f1c0723ec0..d93808a48ee 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -764,3 +764,10 @@ config FSL_IMX95
     select IMX_LPI2C
     select SDHCI
     select UNIMP
+
+config FSL_IMX95_EVK
+    bool
+    default y
+    depends on AARCH64
+    depends on TCG
+    select FSL_IMX95
diff --git a/hw/arm/imx95-evk.c b/hw/arm/imx95-evk.c
new file mode 100644
index 00000000000..470ca112376
--- /dev/null
+++ b/hw/arm/imx95-evk.c
@@ -0,0 +1,136 @@
+/*
+ * NXP i.MX 95 19x19 Evaluation Kit (LPDDR5) - QEMU machine
+ *
+ * Modeled on hw/arm/imx8mp-evk.c by Bernhard Beschow
+ *
+ * Copyright (c) 2026, Kyle Fox
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Instantiates the SoC, attaches DDR, and hands control to
+ * arm_load_kernel() so -kernel works.
+ */
+
+#include "qemu/osdep.h"
+#include "system/address-spaces.h"
+#include "hw/arm/boot.h"
+#include "hw/arm/fsl-imx95.h"
+#include "hw/arm/machines-qom.h"
+#include "hw/core/boards.h"
+#include "hw/core/qdev-properties.h"
+#include "system/kvm.h"
+#include "system/qtest.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+
+static void imx95_evk_init(MachineState *machine)
+{
+    static struct arm_boot_info boot_info;
+    FslImx95State *s;
+
+    if (kvm_enabled()) {
+        error_report("The imx95-19x19-evk machine requires TCG: it emulates "
+                     "Cortex-M33 and Cortex-M7 cores that KVM cannot run");
+        exit(1);
+    }
+
+    if (machine->ram_size > FSL_IMX95_RAM_SIZE_MAX) {
+        error_report("RAM size " RAM_ADDR_FMT
+                     " above max supported (0x%" PRIx64 ")",
+                     machine->ram_size, (uint64_t)FSL_IMX95_RAM_SIZE_MAX);
+        exit(1);
+    }
+
+    /*
+     * The board presets the DTB at RAM_START + 128 MiB and the initrd at
+     * RAM_START + 256 MiB (see below), so require enough RAM to hold them.
+     */
+    if (machine->ram_size < FSL_IMX95_RAM_SIZE_MIN) {
+        error_report("RAM size " RAM_ADDR_FMT
+                     " below min supported (0x%" PRIx64 ")",
+                     machine->ram_size, (uint64_t)FSL_IMX95_RAM_SIZE_MIN);
+        exit(1);
+    }
+
+    boot_info = (struct arm_boot_info) {
+        .loader_start = FSL_IMX95_RAM_START,
+        .board_id     = -1,
+        .ram_size     = machine->ram_size,
+        .psci_conduit = QEMU_PSCI_CONDUIT_SMC,
+        /*
+         * The default arm_load_kernel() heuristic lands the initrd (and the
+         * DTB right after it) at loader_start + 128 MiB == 0x88000000. That
+         * is exactly where the NXP BSP device tree places the Cortex-M7
+         * remoteproc carveout (vdev vrings + rsc-table @0x88220000). The
+         * collision makes Linux reserve those pages for the initrd/FDT, so
+         * the later reserved-memory no-map pass fails (-EBUSY) and imx-rproc
+         * cannot ioremap the rsc-table (probe fails with -ENOMEM). Force the
+         * initrd/DTB up to 0x90000000, in the free gap above the M7 carveout
+         * and below the GPU/VPU carveouts at 0xa0000000.
+         */
+        .initrd_start = FSL_IMX95_RAM_START + 256 * MiB,
+    };
+
+    s = FSL_IMX95(object_new(TYPE_FSL_IMX95));
+    object_property_add_child(OBJECT(machine), "soc", OBJECT(s));
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal);
+
+    memory_region_add_subregion(get_system_memory(), FSL_IMX95_RAM_START,
+                                machine->ram);
+
+    if (!qtest_enabled()) {
+        arm_load_kernel(&s->cpu[0], machine, &boot_info);
+
+        /*
+         * arm_load_kernel() registers its boot reset hook on every CPU in
+         * the system and treats all non-boot cores as A-profile PSCI
+         * secondaries. That is correct for the five A55 secondaries, but
+         * the Cortex-M33 SM core and the Cortex-M7 RT core are not part of
+         * the A55 boot flow - each boots from its own ITCM vector table.
+         * Detach them from the A-profile boot machinery so their reset just
+         * runs the normal M-profile vector-table reset. Whether each then
+         * actually runs is decided by the SoC's M33/M7 reset hooks (only if
+         * firmware was loaded into the respective ITCM).
+         */
+        if (s->m33.cpu) {
+            s->m33.cpu->env.boot_info = NULL;
+        }
+        if (s->m7.cpu) {
+            s->m7.cpu->env.boot_info = NULL;
+        }
+    }
+}
+
+static const char *imx95_evk_get_default_cpu_type(const MachineState *ms)
+{
+    return ARM_CPU_TYPE_NAME("cortex-a55");
+}
+
+static const char * const imx95_evk_valid_cpu_types[] = {
+    ARM_CPU_TYPE_NAME("cortex-a55"),
+    NULL
+};
+
+static void imx95_19x19_evk_machine_init(MachineClass *mc)
+{
+    mc->desc                  = "NXP i.MX 95 19x19 EVK (LPDDR5)";
+    mc->init                  = imx95_evk_init;
+    /*
+     * Total vCPUs = 6 A55 + 1 Cortex-M33 System Manager core + 1 Cortex-M7
+     * real-time core that the SoC always instantiates. TCG sizes its
+     * per-CPU context table from the resolved smp.max_cpus, which defaults
+     * to smp.cpus when -smp is not given - so both the default and the
+     * max must include the M33 and the M7, otherwise the 8th CPU's
+     * tcg_register_thread() asserts. The A55 cluster size is fixed in
+     * the SoC regardless of -smp; this count is really "A55 cluster +
+     * SM core + RT core".
+     */
+    mc->default_cpus          = FSL_IMX95_NUM_A55_CPUS + 2;
+    mc->max_cpus              = FSL_IMX95_NUM_A55_CPUS + 2;
+    mc->default_ram_id        = "imx95-19x19-evk.ram";
+    mc->default_ram_size      = 8 * GiB;   /* 19x19 EVK has 8 GiB LPDDR5 */
+    mc->get_default_cpu_type  = imx95_evk_get_default_cpu_type;
+    mc->valid_cpu_types       = imx95_evk_valid_cpu_types;
+}
+
+DEFINE_MACHINE_AARCH64("imx95-19x19-evk", imx95_19x19_evk_machine_init)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 96fef4c5b5f..cdb61fa1612 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -118,5 +118,6 @@ arm_common_ss.add(when: ['CONFIG_AXIADO_EVK', 'TARGET_AARCH64'], if_true: files(
 
 arm_common_ss.add(files('boot.c'))
 arm_common_ss.add(when: 'CONFIG_FSL_IMX95', if_true: files('fsl-imx95.c'))
+arm_common_ss.add(when: 'CONFIG_FSL_IMX95_EVK', if_true: files('imx95-evk.c'))
 
 hw_common_arch += {'arm': arm_common_ss}
-- 
2.34.1
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.